// @File: src/lib/whatsappApi.ts
// @Desc: Typed fetch wrappers for the client-facing WhatsApp chat endpoints.
//        Auth mirrors uptimerobotApi.ts — JWT from localStorage.
import { getToken } from "@/lib/auth";

function authHeaders(extra?: Record<string, string>): HeadersInit {
  const token = getToken();
  return {
    "Content-Type": "application/json",
    ...(token ? { Authorization: `Bearer ${token}` } : {}),
    ...(extra ?? {}),
  };
}

async function handle<T>(res: Response): Promise<T> {
  const raw = await res.text();
  let data: unknown = null;
  if (raw) {
    try {
      data = JSON.parse(raw);
    } catch {
      data = raw;
    }
  }
  if (!res.ok) {
    const detail =
      (data && typeof data === "object" && "detail" in (data as Record<string, unknown>)
        ? String((data as Record<string, unknown>).detail)
        : null) ||
      res.statusText ||
      "Request failed";
    throw new Error(detail);
  }
  return data as T;
}

// ---------- types ----------
export type WhatsappDeliveryStatus =
  | "queued"
  | "sent"
  | "delivered"
  | "read"
  | "failed"
  | "received";

export interface WhatsappMessage {
  id: number;
  contact_id: number | null;
  device_id: number | null;
  direction: "inbound" | "outbound";
  message_type: "text" | "image" | "video" | "audio" | "document" | "template" | "system";
  body: string | null;
  media_url: string | null;
  media_mime: string | null;
  delivery_status: WhatsappDeliveryStatus;
  is_read: boolean;
  created_at: string | null;
}

export interface WhatsappConversation {
  contact_id: number;
  phone_number: string;
  display_name: string | null;
  tags: string | null;
  is_opted_out: boolean;
  last_message_at: string | null;
  unread_count: number;
  last_message: {
    id: number;
    direction: "inbound" | "outbound";
    message_type: string;
    body: string | null;
    delivery_status: WhatsappDeliveryStatus;
    created_at: string | null;
  } | null;
}

export interface WhatsappSendPayload {
  contact_id?: number | null;
  phone_number?: string | null;
  body?: string | null;
  message_type?: "text" | "image" | "video" | "audio" | "document";
  media_url?: string | null;
  media_mime?: string | null;
}

// ---------- calls ----------
export async function waListConversations(params: {
  unreadOnly?: boolean;
  search?: string;
}): Promise<WhatsappConversation[]> {
  const qs = new URLSearchParams();
  if (params.unreadOnly) qs.set("unread_only", "true");
  if (params.search) qs.set("search", params.search);
  const url = `/api/client/whatsapp/conversations${qs.toString() ? `?${qs}` : ""}`;
  const res = await fetch(url, { headers: authHeaders() });
  const payload = await handle<{ items: WhatsappConversation[]; total: number }>(res);
  return payload.items || [];
}

export async function waListMessages(
  contactId: number,
  opts?: { limit?: number; beforeId?: number },
): Promise<WhatsappMessage[]> {
  const qs = new URLSearchParams();
  if (opts?.limit) qs.set("limit", String(opts.limit));
  if (opts?.beforeId) qs.set("before_id", String(opts.beforeId));
  const url = `/api/client/whatsapp/conversations/${contactId}/messages${
    qs.toString() ? `?${qs}` : ""
  }`;
  const res = await fetch(url, { headers: authHeaders() });
  const payload = await handle<{ items: WhatsappMessage[]; total: number }>(res);
  return payload.items || [];
}

export async function waSendMessage(
  contactId: number | null,
  payload: WhatsappSendPayload,
): Promise<WhatsappMessage> {
  const url =
    contactId !== null
      ? `/api/client/whatsapp/conversations/${contactId}/messages`
      : `/api/client/whatsapp/messages`;
  const res = await fetch(url, {
    method: "POST",
    headers: authHeaders(),
    body: JSON.stringify(payload),
  });
  return handle<WhatsappMessage>(res);
}

export async function waMarkRead(contactId: number): Promise<{ updated: number }> {
  const res = await fetch(`/api/client/whatsapp/conversations/${contactId}/read`, {
    method: "POST",
    headers: authHeaders(),
  });
  return handle<{ updated: number }>(res);
}

// ---------------------------------------------------------------------------
// Admin console — wrappers for /api/v1/admin/whatsapp/*
// ---------------------------------------------------------------------------
const ADMIN_BASE = "/api/v1/admin/whatsapp";

export interface WhatsappPackage {
  id: number;
  slug: string;
  name: string;
  description: string | null;
  tagline: string | null;

  max_devices: number;
  devices_unlimited: boolean;
  messages_per_month: number;
  messages_unlimited: boolean;
  max_contacts: number;
  contacts_unlimited: boolean;
  max_campaigns: number;
  campaigns_unlimited: boolean;
  max_workflows: number;
  workflows_unlimited: boolean;
  max_team_operators: number;
  team_operators_unlimited: boolean;
  chat_retention_days: number;

  auto_reply_allowed: boolean;
  ai_assistant_allowed: boolean;
  workflow_automation_allowed: boolean;
  webhook_access_allowed: boolean;
  api_access_allowed: boolean;
  media_sending_allowed: boolean;
  template_messages_allowed: boolean;

  whmcs_product_id: number | null;
  terminate_action: string;

  is_active: boolean;
  is_featured: boolean;
  price_per_month: string | null;
  sort_order: number;

  created_at: string;
  updated_at: string;
}

export type WhatsappPackagePayload = Partial<Omit<WhatsappPackage, "id" | "created_at" | "updated_at">> & {
  slug: string;
  name: string;
};

export interface WhatsappSubscription {
  id: number;
  user_id: string;
  user_email: string | null;
  package_id: number;
  whmcs_service_id: number | null;
  whmcs_product_id: number | null;
  status: "active" | "suspended" | "terminated" | string;
  devices_used: number;
  messages_used_month: number;
  contacts_used: number;
  campaigns_used: number;
  workflows_used: number;
  usage_period_start: string | null;
  notes: string | null;
  activated_at: string | null;
  suspended_at: string | null;
  terminated_at: string | null;
  created_at: string;
  updated_at: string;
  package: WhatsappPackage | null;
}

export interface WhatsappAuditLogEntry {
  id: number;
  actor_user_id: string | null;
  actor_email: string | null;
  action: string;
  target_type: string | null;
  target_id: string | null;
  detail: string | null;
  created_at: string;
}

export interface WhatsappApiLogEntry {
  id: number;
  subscription_id: number | null;
  user_id: string | null;
  channel: string;
  method: string;
  endpoint: string;
  status_code: number | null;
  duration_ms: number | null;
  error_detail: string | null;
  created_at: string;
}

export interface WhatsappIntegrationResult {
  ok: boolean;
  message: string;
  latency_ms: number | null;
  upstream_base_url: string | null;
}

export interface WhatsappAdminOverview {
  total_packages: number;
  active_packages: number;
  total_subscriptions: number;
  active_subscriptions: number;
  suspended_subscriptions: number;
  terminated_subscriptions: number;
  total_devices: number;
  connected_devices: number;
  messages_last_24h: number;
  recent_audit: WhatsappAuditLogEntry[];
}

export async function waAdminOverview(): Promise<WhatsappAdminOverview> {
  const res = await fetch(`${ADMIN_BASE}/overview`, { headers: authHeaders() });
  return handle<WhatsappAdminOverview>(res);
}

export async function waAdminTestIntegration(): Promise<WhatsappIntegrationResult> {
  const res = await fetch(`${ADMIN_BASE}/integration/test`, {
    method: "POST",
    headers: authHeaders(),
  });
  return handle<WhatsappIntegrationResult>(res);
}

export async function waAdminListPackages(includeInactive = true): Promise<WhatsappPackage[]> {
  const qs = includeInactive ? "?include_inactive=true" : "?include_inactive=false";
  const res = await fetch(`${ADMIN_BASE}/packages${qs}`, { headers: authHeaders() });
  return handle<WhatsappPackage[]>(res);
}

export async function waAdminCreatePackage(payload: WhatsappPackagePayload): Promise<WhatsappPackage> {
  const res = await fetch(`${ADMIN_BASE}/packages`, {
    method: "POST",
    headers: authHeaders(),
    body: JSON.stringify(payload),
  });
  return handle<WhatsappPackage>(res);
}

export async function waAdminUpdatePackage(
  packageId: number,
  payload: Partial<WhatsappPackagePayload>,
): Promise<WhatsappPackage> {
  const res = await fetch(`${ADMIN_BASE}/packages/${packageId}`, {
    method: "PUT",
    headers: authHeaders(),
    body: JSON.stringify(payload),
  });
  return handle<WhatsappPackage>(res);
}

export async function waAdminDeletePackage(packageId: number): Promise<void> {
  const res = await fetch(`${ADMIN_BASE}/packages/${packageId}`, {
    method: "DELETE",
    headers: authHeaders(),
  });
  if (!res.ok && res.status !== 204) {
    throw new Error(`Failed to delete package (${res.status})`);
  }
}

export async function waAdminListSubscriptions(params?: {
  status?: string;
  search?: string;
}): Promise<WhatsappSubscription[]> {
  const qs = new URLSearchParams();
  if (params?.status) qs.set("status", params.status);
  if (params?.search) qs.set("search", params.search);
  const url = `${ADMIN_BASE}/subscriptions${qs.toString() ? `?${qs}` : ""}`;
  const res = await fetch(url, { headers: authHeaders() });
  return handle<WhatsappSubscription[]>(res);
}

export async function waAdminSubscriptionAction(
  subId: number,
  action: "suspend" | "resume" | "terminate",
): Promise<{ subscription: WhatsappSubscription; message: string }> {
  const res = await fetch(`${ADMIN_BASE}/subscriptions/${subId}/${action}`, {
    method: "POST",
    headers: authHeaders(),
  });
  return handle<{ subscription: WhatsappSubscription; message: string }>(res);
}

export async function waAdminAuditLog(limit = 200): Promise<WhatsappAuditLogEntry[]> {
  const res = await fetch(`${ADMIN_BASE}/audit-log?limit=${limit}`, {
    headers: authHeaders(),
  });
  return handle<WhatsappAuditLogEntry[]>(res);
}

export async function waAdminApiLog(limit = 200): Promise<WhatsappApiLogEntry[]> {
  const res = await fetch(`${ADMIN_BASE}/api-log?limit=${limit}`, {
    headers: authHeaders(),
  });
  return handle<WhatsappApiLogEntry[]>(res);
}

// ---------- AI auto-reply ----------
export interface WhatsappAiSettings {
  subscription_id: number;
  auto_reply_enabled: boolean;
  knowledge_base: string;
  faq_training: string;
  tone: "friendly" | "professional" | "concise" | "casual" | string;
  language: "en" | "ar" | "auto" | string;
  confidence_threshold: number;
  business_hours_only: boolean;
  business_hours_start: string;
  business_hours_end: string;
  human_handoff_enabled: boolean;
  preview_before_sending: boolean;
  sentiment_detection_enabled: boolean;
  suggested_replies_enabled: boolean;
  spam_detection_enabled: boolean;
  lead_qualification_enabled: boolean;
  updated_at: string | null;
}

export interface WhatsappAiPreviewResult {
  reply: string;
  gated: boolean;
  reason: string | null;
  model: string;
  usage: Record<string, unknown> | null;
}

const AI_BASE = "/api/v1/client/whatsapp/ai";

export async function waGetAiSettings(): Promise<WhatsappAiSettings> {
  const res = await fetch(`${AI_BASE}/settings`, { headers: authHeaders() });
  return handle<WhatsappAiSettings>(res);
}

export async function waUpdateAiSettings(
  patch: Partial<WhatsappAiSettings>,
): Promise<WhatsappAiSettings> {
  const res = await fetch(`${AI_BASE}/settings`, {
    method: "PATCH",
    headers: authHeaders(),
    body: JSON.stringify(patch),
  });
  return handle<WhatsappAiSettings>(res);
}

export async function waPreviewAiReply(
  incomingMessage: string,
  force = true,
): Promise<WhatsappAiPreviewResult> {
  const res = await fetch(`${AI_BASE}/preview`, {
    method: "POST",
    headers: authHeaders(),
    body: JSON.stringify({ incoming_message: incomingMessage, force }),
  });
  return handle<WhatsappAiPreviewResult>(res);
}

// ---------------------------------------------------------------------------
// Campaigns (bulk messaging)
// ---------------------------------------------------------------------------
const CLIENT_BASE = "/api/v1/client/whatsapp";

export type WhatsappCampaignStatus =
  | "draft"
  | "scheduled"
  | "running"
  | "paused"
  | "completed"
  | "failed";

export interface WhatsappCampaign {
  id: number;
  name: string;
  message_body: string;
  media_url: string | null;
  status: WhatsappCampaignStatus;
  scheduled_at: string | null;
  started_at: string | null;
  completed_at: string | null;
  total_recipients: number;
  sent_count: number;
  delivered_count: number;
  replied_count: number;
  failed_count: number;
  throttle_per_minute: number;
  last_error: string | null;
  created_at: string;
  updated_at: string;
}

export interface WhatsappCampaignRecipient {
  id: number;
  phone_number: string;
  contact_id: number | null;
  status: "pending" | "sent" | "delivered" | "replied" | "failed" | "skipped";
  error_detail: string | null;
  sent_at: string | null;
}

export interface WhatsappCampaignPayload {
  name: string;
  message_body: string;
  media_url?: string | null;
  scheduled_at?: string | null;
  throttle_per_minute?: number;
  recipients: { phone_number: string; contact_id?: number | null }[];
}

export interface WhatsappCampaignStats {
  campaigns: number;
  recipients: number;
  sent: number;
  delivered: number;
  replied: number;
  failed: number;
}

export async function waListCampaigns(): Promise<WhatsappCampaign[]> {
  const res = await fetch(`${CLIENT_BASE}/campaigns`, { headers: authHeaders() });
  return handle<WhatsappCampaign[]>(res);
}

export async function waCampaignStats(): Promise<WhatsappCampaignStats> {
  const res = await fetch(`${CLIENT_BASE}/campaigns/stats`, { headers: authHeaders() });
  return handle<WhatsappCampaignStats>(res);
}

export async function waGetCampaign(id: number): Promise<WhatsappCampaign> {
  const res = await fetch(`${CLIENT_BASE}/campaigns/${id}`, { headers: authHeaders() });
  return handle<WhatsappCampaign>(res);
}

export async function waListCampaignRecipients(
  id: number,
  limit = 500,
): Promise<WhatsappCampaignRecipient[]> {
  const res = await fetch(`${CLIENT_BASE}/campaigns/${id}/recipients?limit=${limit}`, {
    headers: authHeaders(),
  });
  return handle<WhatsappCampaignRecipient[]>(res);
}

export async function waCreateCampaign(
  payload: WhatsappCampaignPayload,
): Promise<WhatsappCampaign> {
  const res = await fetch(`${CLIENT_BASE}/campaigns`, {
    method: "POST",
    headers: authHeaders(),
    body: JSON.stringify(payload),
  });
  return handle<WhatsappCampaign>(res);
}

export async function waUpdateCampaign(
  id: number,
  patch: Partial<WhatsappCampaignPayload>,
): Promise<WhatsappCampaign> {
  const res = await fetch(`${CLIENT_BASE}/campaigns/${id}`, {
    method: "PATCH",
    headers: authHeaders(),
    body: JSON.stringify(patch),
  });
  return handle<WhatsappCampaign>(res);
}

export async function waDeleteCampaign(id: number): Promise<void> {
  const res = await fetch(`${CLIENT_BASE}/campaigns/${id}`, {
    method: "DELETE",
    headers: authHeaders(),
  });
  if (!res.ok && res.status !== 204) {
    throw new Error(`Failed to delete campaign (${res.status})`);
  }
}

export async function waTransitionCampaign(
  id: number,
  action: "start" | "pause" | "resume" | "cancel",
): Promise<WhatsappCampaign> {
  const res = await fetch(`${CLIENT_BASE}/campaigns/${id}/${action}`, {
    method: "POST",
    headers: authHeaders(),
  });
  return handle<WhatsappCampaign>(res);
}

// ---------------------------------------------------------------------------
// Campaign analytics
// ---------------------------------------------------------------------------

const ANALYTICS_BASE = `${CLIENT_BASE}/campaigns/analytics`;

export interface CampaignAnalyticsKpis {
  total_campaigns: number;
  total_recipients: number;
  sent: number;
  delivered: number;
  read: number;
  replied: number;
  failed: number;
  delivery_rate: number;
  reply_rate: number;
  failure_rate: number;
}

export interface CampaignAnalyticsTrendPoint {
  bucket_ts: string;
  sent: number;
  delivered: number;
  read: number;
  replied: number;
  failed: number;
  opted_out: number;
  delivery_rate: number;
  reply_rate: number;
}

export interface CampaignAnalyticsRange {
  start: string;
  end: string;
  granularity: string;
}

export interface CampaignAnalyticsOverview {
  kpis: CampaignAnalyticsKpis;
  trend: CampaignAnalyticsTrendPoint[];
  range: CampaignAnalyticsRange;
}

export interface CampaignTimeseries {
  campaign: {
    id: number;
    name: string;
    status: string;
    total_recipients: number;
    sent_count: number;
    delivered_count: number;
    replied_count: number;
    failed_count: number;
  };
  range: CampaignAnalyticsRange;
  series: CampaignAnalyticsTrendPoint[];
}

export interface CampaignFunnelStep {
  label: string;
  value: number;
  rate: number;
}

export interface CampaignFunnel {
  campaign: { id: number; name: string };
  steps: CampaignFunnelStep[];
  failed: number;
}

export interface CampaignCompareItem {
  id: number;
  name: string;
  status: string;
  total_recipients: number;
  sent: number;
  delivered: number;
  replied: number;
  failed: number;
  delivery_rate: number;
  reply_rate: number;
  failure_rate: number;
  created_at: string | null;
  completed_at: string | null;
}

export interface CampaignCompare {
  campaigns: CampaignCompareItem[];
}

export async function waAnalyticsOverview(
  params: { start?: string; end?: string; granularity?: string } = {},
): Promise<CampaignAnalyticsOverview> {
  const qs = new URLSearchParams();
  if (params.start) qs.set("start", params.start);
  if (params.end) qs.set("end", params.end);
  if (params.granularity) qs.set("granularity", params.granularity);
  const url = `${ANALYTICS_BASE}/overview${qs.toString() ? `?${qs}` : ""}`;
  const res = await fetch(url, { headers: authHeaders() });
  return handle<CampaignAnalyticsOverview>(res);
}

export async function waAnalyticsTimeseries(
  id: number,
  params: { start?: string; end?: string; granularity?: string } = {},
): Promise<CampaignTimeseries> {
  const qs = new URLSearchParams();
  if (params.start) qs.set("start", params.start);
  if (params.end) qs.set("end", params.end);
  if (params.granularity) qs.set("granularity", params.granularity);
  const url = `${ANALYTICS_BASE}/${id}/timeseries${qs.toString() ? `?${qs}` : ""}`;
  const res = await fetch(url, { headers: authHeaders() });
  return handle<CampaignTimeseries>(res);
}

export async function waAnalyticsFunnel(id: number): Promise<CampaignFunnel> {
  const res = await fetch(`${ANALYTICS_BASE}/${id}/funnel`, {
    headers: authHeaders(),
  });
  return handle<CampaignFunnel>(res);
}

export async function waAnalyticsCompare(
  ids: number[],
): Promise<CampaignCompare> {
  const qs = new URLSearchParams({ ids: ids.join(",") });
  const res = await fetch(`${ANALYTICS_BASE}/compare?${qs}`, {
    headers: authHeaders(),
  });
  return handle<CampaignCompare>(res);
}

export function waAnalyticsSummaryCsvUrl(): string {
  return `${ANALYTICS_BASE}/export.csv`;
}

export function waAnalyticsCampaignCsvUrl(id: number): string {
  return `${ANALYTICS_BASE}/${id}/export.csv`;
}

/** Helper that triggers a browser download using the current auth token. */
export async function waAnalyticsDownload(url: string, filename: string): Promise<void> {
  const res = await fetch(url, { headers: authHeaders() });
  if (!res.ok) throw new Error(`Download failed (${res.status})`);
  const blob = await res.blob();
  const objectUrl = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = objectUrl;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  setTimeout(() => URL.revokeObjectURL(objectUrl), 1_000);
}

// ---------------------------------------------------------------------------
// Workflow automation
// ---------------------------------------------------------------------------

export type WhatsappWorkflowTriggerType =
  | "new_message"
  | "keyword"
  | "first_message"
  | "outside_hours"
  | "no_reply_after"
  | "added_to_campaign";

export type WhatsappWorkflowActionType =
  | "send_message"
  | "assign_tag"
  | "mark_lead"
  | "notify_admin";

export interface WhatsappWorkflowTrigger {
  type: WhatsappWorkflowTriggerType;
  keyword?: string | null;
  minutes?: number | null;
  campaign_id?: number | null;
}

export interface WhatsappWorkflowAction {
  type: WhatsappWorkflowActionType;
  body?: string | null;
  tag?: string | null;
  note?: string | null;
}

export interface WhatsappWorkflow {
  id: number;
  name: string;
  description: string | null;
  is_active: boolean;
  trigger: WhatsappWorkflowTrigger;
  actions: WhatsappWorkflowAction[];
  last_run_at: string | null;
  run_count: number;
  created_at: string;
  updated_at: string;
}

export interface WhatsappWorkflowRun {
  id: number;
  workflow_id: number;
  trigger_detail: string | null;
  result: "ok" | "error" | "skipped";
  error_detail: string | null;
  executed_actions: number;
  created_at: string;
}

export interface WhatsappWorkflowPayload {
  name: string;
  description?: string | null;
  is_active?: boolean;
  trigger: WhatsappWorkflowTrigger;
  actions: WhatsappWorkflowAction[];
}

export interface WhatsappWorkflowTestResult {
  matched: boolean;
  event: string;
  trigger: WhatsappWorkflowTrigger;
  preview_actions: Array<{
    type: string;
    status: "would_run" | "skipped";
    reason?: string;
    body?: string | null;
    tag?: string | null;
  }>;
}

export async function waListWorkflows(): Promise<WhatsappWorkflow[]> {
  const res = await fetch(`${CLIENT_BASE}/workflows`, { headers: authHeaders() });
  return handle<WhatsappWorkflow[]>(res);
}

export async function waCreateWorkflow(
  payload: WhatsappWorkflowPayload,
): Promise<WhatsappWorkflow> {
  const res = await fetch(`${CLIENT_BASE}/workflows`, {
    method: "POST",
    headers: authHeaders(),
    body: JSON.stringify(payload),
  });
  return handle<WhatsappWorkflow>(res);
}

export async function waUpdateWorkflow(
  id: number,
  patch: Partial<WhatsappWorkflowPayload>,
): Promise<WhatsappWorkflow> {
  const res = await fetch(`${CLIENT_BASE}/workflows/${id}`, {
    method: "PATCH",
    headers: authHeaders(),
    body: JSON.stringify(patch),
  });
  return handle<WhatsappWorkflow>(res);
}

export async function waDeleteWorkflow(id: number): Promise<void> {
  const res = await fetch(`${CLIENT_BASE}/workflows/${id}`, {
    method: "DELETE",
    headers: authHeaders(),
  });
  if (!res.ok && res.status !== 204) {
    throw new Error(`Failed to delete workflow (${res.status})`);
  }
}

export async function waListWorkflowRuns(id: number): Promise<WhatsappWorkflowRun[]> {
  const res = await fetch(`${CLIENT_BASE}/workflows/${id}/runs`, {
    headers: authHeaders(),
  });
  return handle<WhatsappWorkflowRun[]>(res);
}

export async function waTestWorkflow(
  id: number,
  sample_message: string,
  event = "new_message",
): Promise<WhatsappWorkflowTestResult> {
  const res = await fetch(`${CLIENT_BASE}/workflows/${id}/test`, {
    method: "POST",
    headers: authHeaders(),
    body: JSON.stringify({ sample_message, context: { event } }),
  });
  return handle<WhatsappWorkflowTestResult>(res);
}

// ---------------------------------------------------------------------------
// AI-powered campaign insights
// ---------------------------------------------------------------------------

export interface CampaignInsightWarning {
  campaign_id: number | null;
  campaign_name: string;
  severity: "low" | "medium" | "high";
  reason: string;
}

export interface CampaignInsights {
  summary: string;
  trends: string[];
  recommendations: string[];
  warnings: CampaignInsightWarning[];
  model: string;
  fallback: boolean;
  range: Record<string, unknown>;
  kpis: Record<string, unknown>;
}

export async function waAnalyticsInsights(
  params: { start?: string; end?: string; granularity?: string } = {},
): Promise<CampaignInsights> {
  const qs = new URLSearchParams();
  if (params.start) qs.set("start", params.start);
  if (params.end) qs.set("end", params.end);
  if (params.granularity) qs.set("granularity", params.granularity);
  const url = `${ANALYTICS_BASE}/insights${qs.toString() ? `?${qs}` : ""}`;
  const res = await fetch(url, { headers: authHeaders() });
  return handle<CampaignInsights>(res);
}