/**
 * Meta WhatsApp Cloud API client (Graph v20+).
 * Pure fetch wrapper — no SDK. The caller passes credentials so the
 * branch_whatsapp_credentials encryption layer stays the only place that
 * touches plaintext tokens.
 */

import { childLogger } from '@server/logger';
const log = childLogger('whatsapp.cloud-api');

const GRAPH_VERSION = 'v20.0';
const GRAPH_BASE = `https://graph.facebook.com/${GRAPH_VERSION}`;

export interface WhatsAppCreds {
  phoneNumberId: string;
  accessToken: string;
}

export interface WhatsAppApiError {
  status: number;
  code?: number;
  type?: string;
  message: string;
  fbtrace_id?: string;
}

interface GraphErrorEnvelope {
  error?: { message?: string; code?: number; type?: string; fbtrace_id?: string };
}

async function callGraph(
  url: string,
  init: RequestInit,
  creds?: { accessToken: string }
): Promise<{ ok: true; json: unknown } | { ok: false; error: WhatsAppApiError }> {
  const headers: Record<string, string> = {
    'Content-Type': 'application/json',
    ...(init.headers as Record<string, string> | undefined),
  };
  if (creds?.accessToken) headers.Authorization = `Bearer ${creds.accessToken}`;

  let res: Response;
  try {
    res = await fetch(url, { ...init, headers });
  } catch (err) {
    return {
      ok: false,
      error: { status: 0, message: err instanceof Error ? err.message : 'Network error' },
    };
  }

  let json: unknown = null;
  try { json = await res.json(); } catch { /* empty body */ }

  if (!res.ok) {
    const env = (json ?? {}) as GraphErrorEnvelope;
    return {
      ok: false,
      error: {
        status: res.status,
        code: env.error?.code,
        type: env.error?.type,
        message: env.error?.message || `HTTP ${res.status}`,
        fbtrace_id: env.error?.fbtrace_id,
      },
    };
  }
  return { ok: true, json };
}

export interface SendTextResult {
  messageId?: string;
  raw: unknown;
}

/**
 * Send a freeform text message. WhatsApp 24-hour customer service window
 * applies — outside that window the caller must use sendTemplate.
 */
export async function sendText(
  creds: WhatsAppCreds,
  toE164: string,
  body: string
): Promise<{ ok: true; result: SendTextResult } | { ok: false; error: WhatsAppApiError }> {
  const url = `${GRAPH_BASE}/${encodeURIComponent(creds.phoneNumberId)}/messages`;
  const payload = {
    messaging_product: 'whatsapp',
    recipient_type: 'individual',
    to: toE164.replace(/^\+/, ''),
    type: 'text',
    text: { body, preview_url: false },
  };
  const r = await callGraph(url, { method: 'POST', body: JSON.stringify(payload) }, creds);
  if (!r.ok) return r;
  const obj = r.json as { messages?: Array<{ id?: string }> };
  return { ok: true, result: { messageId: obj?.messages?.[0]?.id, raw: r.json } };
}

export interface TemplateComponent {
  type: 'header' | 'body' | 'button';
  parameters: Array<{ type: string; text?: string; image?: { link: string } }>;
  sub_type?: string;
  index?: string;
}

/**
 * Send a pre-approved template (required outside the 24h customer service
 * window). Used by the marketing automation in #215.
 */
export async function sendTemplate(
  creds: WhatsAppCreds,
  toE164: string,
  templateName: string,
  languageCode: string,
  components?: TemplateComponent[]
): Promise<{ ok: true; result: SendTextResult } | { ok: false; error: WhatsAppApiError }> {
  const url = `${GRAPH_BASE}/${encodeURIComponent(creds.phoneNumberId)}/messages`;
  const payload = {
    messaging_product: 'whatsapp',
    recipient_type: 'individual',
    to: toE164.replace(/^\+/, ''),
    type: 'template',
    template: {
      name: templateName,
      language: { code: languageCode },
      ...(components?.length ? { components } : {}),
    },
  };
  const r = await callGraph(url, { method: 'POST', body: JSON.stringify(payload) }, creds);
  if (!r.ok) return r;
  const obj = r.json as { messages?: Array<{ id?: string }> };
  return { ok: true, result: { messageId: obj?.messages?.[0]?.id, raw: r.json } };
}

/** Acknowledge an inbound message so the user sees the blue read tick. */
export async function markRead(
  creds: WhatsAppCreds,
  waMessageId: string
): Promise<{ ok: true } | { ok: false; error: WhatsAppApiError }> {
  const url = `${GRAPH_BASE}/${encodeURIComponent(creds.phoneNumberId)}/messages`;
  const payload = { messaging_product: 'whatsapp', status: 'read', message_id: waMessageId };
  const r = await callGraph(url, { method: 'POST', body: JSON.stringify(payload) }, creds);
  return r.ok ? { ok: true } : r;
}

/** Resolve a media id (from an inbound media message) to its CDN URL. */
export async function fetchMediaUrl(
  creds: WhatsAppCreds,
  mediaId: string
): Promise<{ ok: true; url: string; mimeType?: string } | { ok: false; error: WhatsAppApiError }> {
  const url = `${GRAPH_BASE}/${encodeURIComponent(mediaId)}`;
  const r = await callGraph(url, { method: 'GET' }, creds);
  if (!r.ok) return r;
  const obj = r.json as { url?: string; mime_type?: string };
  if (!obj?.url) return { ok: false, error: { status: 502, message: 'Media URL missing in Graph response' } };
  return { ok: true, url: obj.url, mimeType: obj.mime_type };
}

export interface WaTemplate {
  name: string;
  language: string;
  status: string;
  components: unknown[];
}

/**
 * Fetch ALL APPROVED templates for a WABA from the Meta Graph API.
 * Follows cursor-based pagination (`paging.next` / `paging.cursors.after`)
 * until the API signals no further pages. A hard safety cap of MAX_PAGES
 * (1,000 templates) prevents infinite loops should the Meta API ever return
 * a malformed or cycling cursor. When the cap is hit `truncated: true` is
 * returned so the caller can surface a warning.
 */
export async function listApprovedTemplates(
  wabaId: string,
  accessToken: string
): Promise<
  | { ok: true; templates: WaTemplate[]; truncated: boolean }
  | { ok: false; error: WhatsAppApiError }
> {
  const MAX_PAGES = 10;
  const accumulated: WaTemplate[] = [];

  let nextUrl: string | null =
    `${GRAPH_BASE}/${encodeURIComponent(wabaId)}/message_templates` +
    `?fields=name,language,status,components&status=APPROVED&limit=100`;

  let page = 0;
  for (; page < MAX_PAGES && nextUrl; page++) {
    const r = await callGraph(nextUrl, { method: 'GET' }, { accessToken });
    if (!r.ok) return r;
    const obj = r.json as {
      data?: WaTemplate[];
      paging?: { next?: string; cursors?: { after?: string } };
    };
    accumulated.push(...(obj?.data ?? []));

    // Prefer the ready-made `next` URL; fall back to constructing one from
    // `cursors.after` in case Meta omits `next` while the cursor is still set.
    if (obj?.paging?.next) {
      nextUrl = obj.paging.next;
    } else if (obj?.paging?.cursors?.after) {
      const base =
        `${GRAPH_BASE}/${encodeURIComponent(wabaId)}/message_templates` +
        `?fields=name,language,status,components&status=APPROVED&limit=100`;
      nextUrl = `${base}&after=${encodeURIComponent(obj.paging.cursors.after)}`;
    } else {
      nextUrl = null;
    }
  }

  const truncated = page === MAX_PAGES && nextUrl !== null;
  if (truncated) {
    log.warn(
      { wabaId, count: accumulated.length, maxPages: MAX_PAGES },
      'listApprovedTemplates: safety cap reached — some templates were not fetched'
    );
  }

  return { ok: true, templates: accumulated, truncated };
}

/**
 * Verify the credentials by hitting the phone-number node and (optionally)
 * the WABA node so the operator sees both the display phone number and the
 * WhatsApp Business Account display name in the test response.
 */
export async function verifyConnection(
  creds: WhatsAppCreds & { wabaId?: string }
): Promise<
  | { ok: true; displayPhoneNumber?: string; verifiedName?: string; wabaName?: string }
  | { ok: false; error: WhatsAppApiError }
> {
  const url = `${GRAPH_BASE}/${encodeURIComponent(creds.phoneNumberId)}?fields=display_phone_number,verified_name`;
  const r = await callGraph(url, { method: 'GET' }, creds);
  if (!r.ok) return r;
  const obj = r.json as { display_phone_number?: string; verified_name?: string };

  let wabaName: string | undefined;
  if (creds.wabaId) {
    const wabaUrl = `${GRAPH_BASE}/${encodeURIComponent(creds.wabaId)}?fields=name`;
    const w = await callGraph(wabaUrl, { method: 'GET' }, creds);
    if (w.ok) {
      const wObj = w.json as { name?: string };
      wabaName = wObj?.name;
    }
  }

  return {
    ok: true,
    displayPhoneNumber: obj?.display_phone_number,
    verifiedName: obj?.verified_name,
    wabaName,
  };
}
