/**
 * Client-side API wrapper for the marketing automation surface (Task #215).
 * Mirrors the server-side `marketing.validator` shapes so TypeScript catches
 * drift between the form payloads and the route handlers.
 */

import { ApiError } from '@client/lib/apiError';

export { ApiError };

export interface AudienceRules {
  branchIds?: string[];
  orderRecencyDays?: number;
  orderCountMin?: number;
  lifetimeSpendMin?: number;
  tags?: string[];
  channel?: 'any' | 'email' | 'whatsapp';
  includeOptedOut?: boolean;
}

export interface Segment {
  id: string;
  restaurant_id: string;
  name: string;
  description: string | null;
  rules: AudienceRules;
  created_at: string;
  updated_at: string;
}

export interface Campaign {
  id: string;
  restaurant_id: string;
  branch_id: string | null;
  segment_id: string | null;
  audience_rules: AudienceRules;
  name: string;
  channel: 'email' | 'whatsapp';
  subject: string | null;
  body_html: string | null;
  template_name: string | null;
  template_lang: string | null;
  template_vars: string[];
  status: 'draft' | 'scheduled' | 'sending' | 'completed' | 'cancelled' | 'failed';
  scheduled_at: string | null;
  ignore_quiet_hours: boolean;
  audience_count: number;
  sent_count: number;
  failed_count: number;
  opt_out_count: number;
  created_at: string;
  updated_at: string;
  started_at: string | null;
  completed_at: string | null;
  last_error: string | null;
  /** Sibling-group UUID — set when the wizard fans out a single intent into
   *  multiple channels (e.g. Email + WhatsApp). Sibling rows share this id
   *  so the report drawer can aggregate their stats into one view. */
  launch_group_id: string | null;
  /** Set when the linked saved audience was deleted while this campaign was
   *  still non-completed. The campaign keeps its snapshotted audience_rules
   *  but the list view shows a badge so the operator knows the original
   *  segment no longer exists. */
  segment_deleted_at: string | null;
  /** Per-campaign throttles (Task #240). null = no per-campaign cap;
   *  the dispatcher's global per-(branch,channel) batch cap still applies. */
  max_per_minute: number | null;
  max_per_hour: number | null;
}

async function jsonFetch<T>(url: string, init?: RequestInit): Promise<T> {
  const res = await fetch(url, {
    ...init,
    headers: { 'Content-Type': 'application/json', ...(init?.headers ?? {}) },
  });
  const text = await res.text();
  let parsed: unknown = null;
  if (text) { try { parsed = JSON.parse(text); } catch { /* */ } }
  if (!res.ok) {
    const body = (parsed ?? {}) as { error?: string; code?: string; fields?: Record<string, string[]> };
    const msg = body.error ?? `Request failed (${res.status})`;
    // Throw ApiError so callers can pattern-match on `code` / `fields` and
    // surface inline form errors (e.g. duplicate audience name) instead of
    // only being able to render a generic toast from the message string.
    throw new ApiError(msg, res.status, body.code, body.fields);
  }
  return parsed as T;
}

// ─── Segments ───────────────────────────────────────────────────────────────

export async function listSegments(): Promise<Segment[]> {
  const data = await jsonFetch<{ segments: Segment[] }>('/api/marketing/segments');
  return data.segments;
}

export async function createSegment(input: { name: string; description?: string | null; rules: AudienceRules }): Promise<Segment> {
  const data = await jsonFetch<{ segment: Segment }>('/api/marketing/segments', {
    method: 'POST', body: JSON.stringify(input),
  });
  return data.segment;
}

export async function updateSegment(id: string, input: { name?: string; description?: string | null; rules?: AudienceRules }): Promise<Segment> {
  const data = await jsonFetch<{ segment: Segment }>(`/api/marketing/segments/${id}`, {
    method: 'PUT', body: JSON.stringify(input),
  });
  return data.segment;
}

export async function deleteSegment(id: string): Promise<{ detachedCampaignIds: string[] }> {
  const data = await jsonFetch<{ success: true; detachedCampaignIds?: string[] }>(
    `/api/marketing/segments/${id}`, { method: 'DELETE' },
  );
  return { detachedCampaignIds: data.detachedCampaignIds ?? [] };
}

export interface LinkedActiveCampaign {
  id: string;
  name: string;
  status: 'draft' | 'scheduled' | 'sending';
}

/**
 * Pre-check used by the Audiences page before confirming a delete — lists
 * the still-active campaigns that reference this segment so the operator
 * knows how many drafts/scheduled sends will be auto-detached and continue
 * with a snapshot of these rules.
 */
export async function listLinkedActiveCampaigns(id: string): Promise<LinkedActiveCampaign[]> {
  const data = await jsonFetch<{ campaigns: LinkedActiveCampaign[] }>(
    `/api/marketing/segments/${id}/linked-campaigns`,
  );
  return data.campaigns;
}

export interface PreviewResult {
  count: number;
  sample: Array<{ customer_id: string; name: string; email: string | null; phone: string | null }>;
}
export async function previewSegment(rules: AudienceRules, sample = true): Promise<PreviewResult> {
  return jsonFetch<PreviewResult>('/api/marketing/segments/preview', {
    method: 'POST', body: JSON.stringify({ rules, sample }),
  });
}

// ─── Campaigns ──────────────────────────────────────────────────────────────

export interface ListCampaignsParams {
  /** Restrict to campaigns whose source audience/segment was deleted. */
  audienceDeleted?: boolean;
}
export async function listCampaigns(params: ListCampaignsParams = {}): Promise<Campaign[]> {
  const qs = params.audienceDeleted ? '?audienceDeleted=1' : '';
  const data = await jsonFetch<{ campaigns: Campaign[] }>(`/api/marketing/campaigns${qs}`);
  return data.campaigns;
}

export async function getCampaign(id: string): Promise<Campaign> {
  const data = await jsonFetch<{ campaign: Campaign }>(`/api/marketing/campaigns/${id}`);
  return data.campaign;
}

export interface CampaignDraft {
  name: string;
  channel: 'email' | 'whatsapp';
  branchId?: string | null;
  segmentId?: string | null;
  audienceRules?: AudienceRules;
  subject?: string | null;
  bodyHtml?: string | null;
  templateName?: string | null;
  templateLang?: string | null;
  templateVars?: string[];
  scheduledAt?: string | null;
  ignoreQuietHours?: boolean;
  launchGroupId?: string | null;
  /** Per-campaign throttles (Task #240). Optional positive integers. */
  maxPerMinute?: number | null;
  maxPerHour?: number | null;
}

export async function createCampaign(input: CampaignDraft): Promise<Campaign> {
  const data = await jsonFetch<{ campaign: Campaign }>('/api/marketing/campaigns', {
    method: 'POST', body: JSON.stringify(input),
  });
  return data.campaign;
}

export async function updateCampaign(id: string, input: Partial<CampaignDraft>): Promise<Campaign> {
  const data = await jsonFetch<{ campaign: Campaign }>(`/api/marketing/campaigns/${id}`, {
    method: 'PUT', body: JSON.stringify(input),
  });
  return data.campaign;
}

export async function deleteCampaign(id: string): Promise<void> {
  await jsonFetch<{ success: true }>(`/api/marketing/campaigns/${id}`, { method: 'DELETE' });
}

export async function launchCampaign(id: string, opts: { sendNow?: boolean; scheduledAt?: string | null }): Promise<Campaign> {
  const data = await jsonFetch<{ campaign: Campaign }>(`/api/marketing/campaigns/${id}/launch`, {
    method: 'POST', body: JSON.stringify(opts),
  });
  return data.campaign;
}

export async function cancelCampaign(id: string): Promise<Campaign> {
  const data = await jsonFetch<{ campaign: Campaign }>(`/api/marketing/campaigns/${id}/launch`, {
    method: 'DELETE',
  });
  return data.campaign;
}

export async function testCampaign(id: string, to?: string): Promise<{ ok: true; messageId: string | null }> {
  return jsonFetch(`/api/marketing/campaigns/${id}/test`, {
    method: 'POST', body: JSON.stringify(to ? { to } : {}),
  });
}

export interface CampaignReport {
  campaign: Campaign;
  totals: { queued: number; sending: number; sent: number; failed: number; skipped: number };
  recent: Array<{ id: string; recipient: string; status: string; sent_at: string | null; last_error: string | null }>;
}
export async function getCampaignReport(id: string): Promise<CampaignReport> {
  return jsonFetch(`/api/marketing/campaigns/${id}/report`);
}

export interface DeliveryListItem {
  id: string;
  campaign_id: string;
  recipient: string;
  /** 'skipped' = permanent failure (bad address, auth error, opted out);
   *  'failed'  = transient failure that exhausted all retries */
  status: 'failed' | 'skipped';
  last_error: string | null;
  attempts: number;
  updated_at: string;
}

export interface ListDeliveriesResult {
  items: DeliveryListItem[];
  total: number;
  page: number;
  pageSize: number;
}

export async function listCampaignDeliveries(
  id: string,
  opts: { page?: number; pageSize?: number } = {},
): Promise<ListDeliveriesResult> {
  const qs = new URLSearchParams();
  if (opts.page) qs.set('page', String(opts.page));
  if (opts.pageSize) qs.set('pageSize', String(opts.pageSize));
  const qStr = qs.toString();
  return jsonFetch<ListDeliveriesResult>(
    `/api/marketing/campaigns/${id}/deliveries${qStr ? `?${qStr}` : ''}`,
  );
}

/** Triggers a browser download of the CSV export for failed/skipped deliveries. */
export function exportCampaignDeliveriesCsv(id: string): void {
  const a = document.createElement('a');
  a.href = `/api/marketing/campaigns/${id}/deliveries?format=csv`;
  a.download = `campaign-${id}-failures.csv`;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
}

// ─── Customer opt-out ───────────────────────────────────────────────────────

export async function setCustomerOptOut(customerId: string, optOut: boolean, reason?: string): Promise<{ ok: true; marketing_opt_out: boolean }> {
  return jsonFetch(`/api/customers/${customerId}/opt-out`, {
    method: optOut ? 'POST' : 'DELETE',
    body: optOut ? JSON.stringify({ reason }) : undefined,
  });
}
