import axios from 'axios';
import { getHttpClient } from './http';
import { contactString } from '../utils/maskedContact';
import { clientSearchTerms } from '../utils/phone';
import { htmlToPlainText } from '../utils/html';
import type { CrmAdapter } from './types';
import type { ActivityKind } from '../utils/activityLabels';
import {
  activityKindFromAction,
  formatTimelineDate,
  formatTimelineInfoLines,
  parseDateMs,
  translateActivity,
  type TimelineDetailLine,
} from '../utils/activityLabels';

/** Endpoint absent on this server (old CRM not yet updated) */
function isMissingEndpoint(err: unknown): boolean {
  return axios.isAxiosError(err) && (err.response?.status === 404 || err.response?.status === 405);
}

export interface DashboardData {
  agentName: string;
  visits: { id: number; reference: string; clientId: number | null; visitDate: string | null }[];
  demandes: { id: number; reference: string; clientId: number | null }[];
  leads: { id: number; reference: string; clientId: number | null }[];
  recentClients: { id: number; name: string; gsm: string | null }[];
  propertyCount: number;
  leadCount: number;
  visitCount: number;
  demandeCount: number;
}

export interface ClientDetail {
  id: number;
  reference: string;
  name: string;
  firstName: string | null;
  lastName: string | null;
  gsm: string | null;
  /** WhatsApp number when different from GSM */
  whatsapp?: string | null;
  email: string | null;
  address: string | null;
  city: string | null;
  classification: string | null;
  note: string | null;
  genreId?: number | null;
  genreName?: string | null;
  engagementId?: number | null;
  otherSourceIds?: number[];
  otherSourceNames?: string[];
  interestedProjectIds?: number[];
  interestedProjectNames?: string[];
  interestedByRooms?: string | null;
  budgetMax?: number | null;
  interestedByVocationId?: number | null;
  agentId?: number | null;
  agentName?: string | null;
  interestedByCountryId?: number | null;
  interestedGouvernoratIds?: number[];
  interestedDelegationIds?: number[];
  interestedLocaliteIds?: number[];
  interestedDistrictIds?: number[];
}

export interface TimelineItem {
  id: number | string;
  title: string;
  /** Raw action slug when available (for icons). */
  action?: string;
  body: string;
  /** Structured detail rows with icons (preferred over raw body). */
  lines?: TimelineDetailLine[];
  date: string;
  type: ActivityKind;
}
async function fetchAgentNameNew(): Promise<string> {
  const { client } = await getHttpClient();
  try {
    const me = await client.get<{ user: { firstName?: string; lastName?: string; username?: string } }>(
      '/me',
    );
    return (
      [me.data.user.firstName, me.data.user.lastName].filter(Boolean).join(' ') ||
      me.data.user.username ||
      'Agent'
    );
  } catch {
    return 'Agent';
  }
}

async function fetchAgentNameLegacy(): Promise<string> {
  const { client } = await getHttpClient();
  try {
    const { data } = await client.get<{ agents?: Record<string, unknown>[] }>(
      '/user/get-user-details',
    );
    const u = data.agents?.[0];
    if (u) {
      return (
        [u.first_name, u.last_name].filter(Boolean).join(' ') || String(u.username ?? 'Agent')
      );
    }
  } catch {
    // ignore
  }
  return 'Agent';
}

/** GET /mobile/sync — same payload on new CRM and updated old CRM */
async function fetchDashboardSync(agentName: string): Promise<DashboardData> {
  const { client } = await getHttpClient();
  const { data } = await client.get<{ data: Record<string, unknown> }>('/mobile/sync');
  const d = data.data;

  const visits = ((d.visits as Record<string, unknown>[]) ?? [])
    .map((v) => {
      const datePart = v.visit_date != null ? String(v.visit_date) : null;
      const timePart =
        v.visit_time != null && String(v.visit_time).slice(0, 5) !== '00:00'
          ? String(v.visit_time).slice(0, 5)
          : null;
      let visitDate = datePart;
      if (datePart && timePart && !/\d{2}:\d{2}/.test(datePart)) {
        visitDate = `${datePart.slice(0, 10)} ${timePart}`;
      }
      return {
        id: Number(v.id),
        reference: String(v.reference ?? ''),
        clientId: v.client_id != null ? Number(v.client_id) : null,
        visitDate,
      };
    })
    .sort((a, b) => {
      const ta = parseDateMs(a.visitDate);
      const tb = parseDateMs(b.visitDate);
      return (Number.isNaN(ta) ? Number.POSITIVE_INFINITY : ta) - (Number.isNaN(tb) ? Number.POSITIVE_INFINITY : tb);
    });

  const demandes = ((d.demandes as Record<string, unknown>[]) ?? []).map((x) => ({
    id: Number(x.id),
    reference: String(x.reference ?? ''),
    clientId: x.client_id != null ? Number(x.client_id) : null,
  }));

  const leads = ((d.leads as Record<string, unknown>[]) ?? []).map((x) => ({
    id: Number(x.id),
    reference: String(x.reference ?? ''),
    clientId: x.client_id != null ? Number(x.client_id) : null,
  }));

  const recentClients = ((d.clients as Record<string, unknown>[]) ?? []).map((c) => ({
    id: Number(c.id),
    name: [c.first_name, c.last_name].filter(Boolean).join(' ') || 'Client',
    gsm: contactString(c.gsm),
  }));

  const counts = (d.counts as Record<string, number>) ?? {};

  return {
    agentName,
    visits,
    demandes,
    leads,
    recentClients,
    propertyCount: Number(counts.properties ?? 0),
    leadCount: Number(counts.openLeads ?? leads.length),
    visitCount: Number(counts.visits ?? visits.length),
    demandeCount: Number(counts.openDemandes ?? demandes.length),
  };
}

export async function fetchDashboard(): Promise<DashboardData> {
  const { config } = await getHttpClient();
  const agentName =
    config.mode === 'legacy' ? await fetchAgentNameLegacy() : await fetchAgentNameNew();

  try {
    return await fetchDashboardSync(agentName);
  } catch (err) {
    // Old CRM without /mobile/sync deployed yet: degrade to name only
    if (config.mode === 'legacy' && isMissingEndpoint(err)) {
      return {
        agentName,
        visits: [],
        demandes: [],
        leads: [],
        recentClients: [],
        propertyCount: 0,
        leadCount: 0,
        visitCount: 0,
        demandeCount: 0,
      };
    }
    throw err;
  }
}

type ClientSearchHit = { id: number; name: string; gsm: string | null };

async function searchClientsOnce(q: string): Promise<ClientSearchHit[]> {
  const { client, config } = await getHttpClient();
  try {
    const { data } = await client.get<{ data: Record<string, unknown>[] }>('/mobile/clients', {
      params: { q },
    });
    return (data.data ?? []).map((c) => ({
      id: Number(c.id),
      name: [c.first_name, c.last_name].filter(Boolean).join(' ') || 'Client',
      gsm: contactString(c.gsm),
    }));
  } catch (err) {
    // Old CRM without /mobile/clients deployed yet: unfiltered legacy list
    if (config.mode !== 'legacy' || !isMissingEndpoint(err)) throw err;
    const { data } = await client.get<{ items?: Record<string, unknown>[] }>(
      '/clients/list',
      { params: { page: 1, num_items_per_page: 20, q } },
    );
    const items = data.items ?? [];
    return items.map((c) => ({
      id: Number(c.id),
      name: String(c.name ?? `${c.first_name ?? ''} ${c.last_name ?? ''}`.trim()),
      gsm: contactString(c.gsm),
    }));
  }
}

export async function searchClients(q: string): Promise<ClientSearchHit[]> {
  let lastError: unknown = null;
  for (const term of clientSearchTerms(q)) {
    try {
      const hits = await searchClientsOnce(term);
      if (hits.length) return hits;
    } catch (err) {
      lastError = err;
    }
  }
  if (lastError) throw lastError;
  return [];
}

function idsFromList(raw: unknown): number[] {
  if (!Array.isArray(raw)) return [];
  return raw
    .map((x) => {
      if (typeof x === 'number') return x;
      if (x && typeof x === 'object' && 'id' in (x as object)) return Number((x as { id: unknown }).id);
      return Number(x);
    })
    .filter((n) => Number.isFinite(n) && n > 0);
}

function mapLegacyClientDetail(c: Record<string, unknown>): ClientDetail {
  const joined =
    [c.first_name ?? c.firstName, c.last_name ?? c.lastName]
      .map((x) => (x != null ? String(x).trim() : ''))
      .filter(Boolean)
      .join(' ')
      .trim() || String(c.name ?? '').trim();
  const name = joined;
  const parts = name.split(/\s+/).filter(Boolean);
  const projectIds = idsFromList(
    c.interested_project_ids ?? c.interestedProjectIds ?? c.interestedByProjects ?? c.interested_projects,
  );
  const gouvIds = idsFromList(
    c.interested_gouvernorat_ids ??
      c.interestedGouvernoratIds ??
      c.interstedByGouvernorats ??
      c.interestedGouvernorats,
  );
  const delIds = idsFromList(
    c.interested_delegation_ids ??
      c.interestedDelegationIds ??
      c.interstedByDelegations ??
      c.interestedDelegations,
  );
  const locIds = idsFromList(
    c.interested_localite_ids ??
      c.interestedLocaliteIds ??
      c.interstedByLocalites ??
      c.interestedLocalites,
  );
  const disIds = idsFromList(
    c.interested_district_ids ??
      c.interestedDistrictIds ??
      c.interstedByDistricts ??
      c.interestedDistricts,
  );
  const pays =
    c.interested_by_country_id ??
    c.interestedByCountryId ??
    (c.interstedByPays && typeof c.interstedByPays === 'object'
      ? (c.interstedByPays as { id?: unknown }).id
      : null);
  const budgetRaw = c.budget_max ?? c.budgetMax;
  const sourceNamesRaw = c.other_source_names ?? c.otherSourceNames;
  const projectNamesRaw = c.interested_project_names ?? c.interestedProjectNames;
  const roomsRaw = c.interested_by_rooms ?? c.interestedByRooms ?? c.room_type ?? c.roomType;
  let agentNameRaw: string | null =
    c.agent_name != null
      ? String(c.agent_name)
      : c.agentName != null
        ? String(c.agentName)
        : null;
  if (!agentNameRaw && c.agent && typeof c.agent === 'object') {
    const agent = c.agent as Record<string, unknown>;
    const fromParts = [agent.firstname ?? agent.firstName ?? agent.first_name, agent.lastname ?? agent.lastName ?? agent.last_name]
      .filter(Boolean)
      .join(' ')
      .trim();
    agentNameRaw = fromParts || (agent.username != null ? String(agent.username) : null);
  }
  return {
    id: Number(c.id),
    reference: String(c.reference ?? c.reference_immo ?? ''),
    name: name || `Client #${c.id}`,
    firstName: c.first_name != null ? String(c.first_name) : parts[0] ?? null,
    lastName: c.last_name != null ? String(c.last_name) : parts.slice(1).join(' ') || null,
    gsm: contactString(c.gsm),
    whatsapp: contactString(c.whatsapp ?? c.gsmWhatsapp ?? c.whatsApp),
    email: contactString(c.email_web ?? c.email),
    address: c.adresse != null ? String(c.adresse) : c.address != null ? String(c.address) : null,
    city: c.city != null ? String(c.city) : c.ville != null ? String(c.ville) : null,
    classification:
      c.engagement_name != null
        ? String(c.engagement_name)
        : c.classification != null
          ? String(c.classification)
          : null,
    note: c.note != null ? String(c.note) : null,
    genreId: c.genre_id != null ? Number(c.genre_id) : c.genreId != null ? Number(c.genreId) : null,
    genreName:
      c.genre_name != null
        ? String(c.genre_name)
        : c.genreName != null
          ? String(c.genreName)
          : c.genre && typeof c.genre === 'object' && (c.genre as { name?: unknown }).name != null
            ? String((c.genre as { name: unknown }).name)
            : null,
    engagementId:
      c.engagement_id != null
        ? Number(c.engagement_id)
        : c.engagementId != null
          ? Number(c.engagementId)
          : null,
    otherSourceIds: idsFromList(c.other_source_ids ?? c.otherSourceIds ?? c.otherSources),
    otherSourceNames: Array.isArray(sourceNamesRaw)
      ? sourceNamesRaw.map((x) => String(x)).filter(Boolean)
      : [],
    interestedProjectIds: projectIds,
    interestedProjectNames: Array.isArray(projectNamesRaw)
      ? projectNamesRaw.map((x) => String(x)).filter(Boolean)
      : [],
    interestedByRooms: roomsRaw != null && roomsRaw !== '' ? String(roomsRaw) : null,
    budgetMax: budgetRaw != null && budgetRaw !== '' ? Number(budgetRaw) : null,
    interestedByVocationId:
      c.interested_by_vocation_id != null
        ? Number(c.interested_by_vocation_id)
        : c.interestedByVocationId != null
          ? Number(c.interestedByVocationId)
          : null,
    agentId:
      c.agent_id != null
        ? Number(c.agent_id)
        : c.agentId != null
          ? Number(c.agentId)
          : c.agent && typeof c.agent === 'object'
            ? Number((c.agent as { id?: unknown }).id)
            : null,
    agentName: agentNameRaw ? String(agentNameRaw) : null,
    interestedByCountryId: pays != null ? Number(pays) : null,
    interestedGouvernoratIds: gouvIds,
    interestedDelegationIds: delIds,
    interestedLocaliteIds: locIds,
    interestedDistrictIds: disIds,
  };
}

export async function fetchClientDetail(clientId: number): Promise<ClientDetail | null> {
  const { client, config } = await getHttpClient();
  if (config.mode === 'legacy') {
    // Prefer mobile show (budget / projet / geo) — /clients/details omits many fields.
    try {
      const { data } = await client.get<{ data?: Record<string, unknown> }>(
        `/mobile/clients/${clientId}`,
      );
      if (data.data?.id) return mapLegacyClientDetail(data.data);
    } catch {
      /* fallback below */
    }
    const { data } = await client.get<{ status: number; client?: Record<string, unknown> }>(
      '/clients/details',
      { params: { id: clientId } },
    );
    if (data.status !== 1 || !data.client) return null;
    return mapLegacyClientDetail(data.client);
  }
  const { data } = await client.get<{ data: Record<string, unknown> }>(`/clients/${clientId}`);
  const c = data.data;
  const sourceTags = Array.isArray(c.otherSources)
    ? (c.otherSources as { id?: number }[])
    : Array.isArray(c.other_sources)
      ? (c.other_sources as { id?: number }[])
      : [];
  const projectTags = Array.isArray(c.interestedProjects)
    ? (c.interestedProjects as { id?: number }[])
    : Array.isArray(c.interested_projects)
      ? (c.interested_projects as { id?: number }[])
      : [];
  return {
    id: Number(c.id),
    reference: String(c.reference ?? ''),
    name: [c.first_name, c.last_name].filter(Boolean).join(' ') || String(c.name ?? ''),
    firstName: c.first_name != null ? String(c.first_name) : null,
    lastName: c.last_name != null ? String(c.last_name) : null,
    gsm: contactString(c.gsm),
    whatsapp: contactString(c.whatsapp),
    email: contactString(c.email),
    address: c.address != null ? String(c.address) : null,
    city: c.city != null ? String(c.city) : null,
    classification: c.classification != null ? String(c.classification) : null,
    note: c.note != null ? String(c.note) : null,
    genreId: c.genreId != null ? Number(c.genreId) : c.genre_id != null ? Number(c.genre_id) : null,
    engagementId:
      c.engagementId != null
        ? Number(c.engagementId)
        : c.engagement_id != null
          ? Number(c.engagement_id)
          : null,
    otherSourceIds: sourceTags
      .map((s) => Number(s.id))
      .filter((n) => Number.isFinite(n) && n > 0),
    interestedProjectIds: projectTags
      .map((p) => Number(p.id))
      .filter((n) => Number.isFinite(n) && n > 0),
    budgetMax: c.budgetMax != null ? Number(c.budgetMax) : c.budget_max != null ? Number(c.budget_max) : null,
    interestedByVocationId:
      c.interestedByVocationId != null
        ? Number(c.interestedByVocationId)
        : c.interested_by_vocation_id != null
          ? Number(c.interested_by_vocation_id)
          : null,
    agentId: c.agentId != null ? Number(c.agentId) : c.agent_id != null ? Number(c.agent_id) : null,
    interestedByCountryId:
      c.interestedByCountryId != null
        ? Number(c.interestedByCountryId)
        : c.interested_by_country_id != null
          ? Number(c.interested_by_country_id)
          : null,
    interestedGouvernoratIds: Array.isArray(c.interestedGouvernorats)
      ? (c.interestedGouvernorats as { id?: number }[])
          .map((x) => Number(x.id))
          .filter((n) => n > 0)
      : [],
    interestedDelegationIds: Array.isArray(c.interestedDelegations)
      ? (c.interestedDelegations as { id?: number }[])
          .map((x) => Number(x.id))
          .filter((n) => n > 0)
      : [],
    interestedLocaliteIds: Array.isArray(c.interestedLocalites)
      ? (c.interestedLocalites as { id?: number }[])
          .map((x) => Number(x.id))
          .filter((n) => n > 0)
      : [],
    interestedDistrictIds: Array.isArray(c.interestedDistricts)
      ? (c.interestedDistricts as { id?: number }[])
          .map((x) => Number(x.id))
          .filter((n) => n > 0)
      : [],
  };
}

function mapTimelineRow(t: Record<string, unknown>, index: number): TimelineItem {
  const actionRaw = String(t.action ?? '');
  const label = String(t.action_label ?? t.title ?? '').trim();
  const title =
    label && !/^[a-z0-9_.]+$/i.test(label)
      ? label
      : translateActivity(actionRaw || label || 'Activité');
  const kind = activityKindFromAction(actionRaw || label || title);
  const isRappel = /rappel|reminder|schedule_client/i.test(
    `${actionRaw} ${label} ${title}`,
  );

  const lines: TimelineDetailLine[] = [
    ...formatTimelineInfoLines(t.infos, { omitEndDate: isRappel }),
  ];

  if (t.description != null && typeof t.description !== 'object') {
    const d = htmlToPlainText(String(t.description));
    if (d) lines.unshift({ kind: 'note', text: d });
  }
  if (t.note != null && typeof t.note !== 'object') {
    const n = htmlToPlainText(String(t.note));
    if (n) lines.push({ kind: 'note', label: 'Note', text: n });
  }

  const agent = t.agent as Record<string, unknown> | undefined;
  const agentName = agent
    ? [agent.firstname ?? agent.firstName ?? agent.first_name, agent.lastname ?? agent.lastName ?? agent.last_name]
        .filter(Boolean)
        .join(' ')
    : t.agentName != null
      ? String(t.agentName)
      : '';
  if (agentName && !lines.some((l) => l.kind === 'agent' && l.text.toLowerCase().includes(agentName.toLowerCase()))) {
    lines.push({ kind: 'agent', label: 'Agent', text: agentName });
  }

  if (t.propertyTitle != null) {
    const p = htmlToPlainText(String(t.propertyTitle));
    if (p && !lines.some((l) => l.kind === 'property' && l.text.toLowerCase() === p.toLowerCase())) {
      lines.push({ kind: 'property', label: 'Bien', text: p });
    }
  }
  if (t.projectName != null) {
    const p = htmlToPlainText(String(t.projectName));
    if (p && !lines.some((l) => l.kind === 'project' && l.text.toLowerCase() === p.toLowerCase())) {
      lines.push({ kind: 'project', label: 'Projet', text: p });
    }
  }
  if (t.nextStep != null) {
    lines.push({
      kind: 'other',
      label: 'Prochaine étape',
      text: String(t.customNextStep ?? t.nextStep),
    });
  }

  return {
    id: Number(t.id ?? index),
    title,
    action: actionRaw || undefined,
    body: lines.map((l) => (l.label ? `${l.label} : ${l.text}` : l.text)).join('\n'),
    lines,
    date: formatTimelineDate(t.created_at ?? t.createdAt ?? t.date),
    type: kind,
  };
}

export async function fetchClientTimeline(clientId: number): Promise<TimelineItem[]> {
  const { client, config } = await getHttpClient();
  if (config.mode === 'legacy') {
    try {
      const { data } = await client.get<Record<string, unknown>>(
        `/clients/${clientId}/timeline/list`,
        { params: { num_items_per_page: 40 } },
      );
      const items = (data.items ?? data._embedded ?? []) as Record<string, unknown>[];
      const list = Array.isArray(items)
        ? items
        : Array.isArray((items as { items?: unknown[] }).items)
          ? ((items as { items: Record<string, unknown>[] }).items)
          : [];
      return list.map((t, i) => mapTimelineRow(t, i));
    } catch {
      return [];
    }
  }
  try {
    const { data } = await client.get<{ data: Record<string, unknown>[] }>(
      `/clients/${clientId}/timeline`,
    );
    return (Array.isArray(data.data) ? data.data : []).slice(0, 40).map((t, i) => mapTimelineRow(t, i));
  } catch {
    return [];
  }
}

export function extendAdapter(_adapter: CrmAdapter): void {
  // hooks for future unified adapter methods
}
