/**
 * SiteConversion Dedup Engine
 * ═══════════════════════════
 * Deterministic, idempotent clustering of SiteConversion rows into
 * "keep one, mark rest as duplicates" groups. Running N times produces
 * identical results.
 *
 * Dedup key per row is based on:
 *   - brandId + visitorId
 *   - conversionType + detectionMethod-specific fields
 *   - 5-minute time bucket (events within 5min of each other cluster)
 *
 * Within each cluster, the SURVIVING row is chosen by priority:
 *   tel_link > submit_event > postmessage > native > phone_pattern
 * Ties broken by touchpoint richness, then by earliest timestamp.
 *
 * We NEVER hard-delete — only mark isDuplicateOf pointing to the
 * surviving row's id.
 */

import crypto from "crypto";

export interface DedupRow {
  id: string;
  brandId: string;
  visitorId: string;
  sessionId: string;
  conversionType: string;
  pageUrl: string;
  formId: string | null;
  formFields: unknown;
  detectionMethod: string | null;
  phoneNumber: string | null;
  firstTouchChannel: string | null;
  lastTouchChannel: string | null;
  touchpoints: unknown;
  timestamp: Date;
}

const METHOD_PRIORITY: Record<string, number> = {
  tel_link: 100,
  submit_event: 90,
  postmessage: 80,
  mutation_observer: 70,
  native: 60,
  phone_pattern: 10,
};

function methodScore(method: string | null): number {
  return METHOD_PRIORITY[method ?? ""] ?? 50;
}

function touchpointRichness(row: DedupRow): number {
  let score = 0;
  if (row.firstTouchChannel) score += 1;
  if (row.lastTouchChannel) score += 1;
  if (row.touchpoints && Array.isArray(row.touchpoints) && (row.touchpoints as unknown[]).length > 0) score += 2;
  return score;
}

function bucketTimestamp(ts: Date, minutes = 5): string {
  const ms = ts.getTime();
  const bucket = Math.floor(ms / (minutes * 60_000));
  return String(bucket);
}

export function computeDedupKey(row: DedupRow): string {
  const parts: string[] = [row.brandId, row.visitorId, row.conversionType];

  if (row.conversionType === "form_submit" || row.conversionType === "form_submission") {
    parts.push(row.formId ?? "no_form");
    const fields = Array.isArray(row.formFields)
      ? (row.formFields as Array<{ name?: string }>).map((f) => f.name ?? "").sort().join(",")
      : "";
    parts.push(fields);
  } else if (
    row.conversionType === "phone_call" ||
    row.conversionType === "phone_number_click" ||
    row.conversionType === "click_to_call"
  ) {
    const digits = (row.phoneNumber ?? "").replace(/\D/g, "");
    parts.push(digits || "no_number");
    if (row.detectionMethod === "phone_pattern") {
      parts.push(row.pageUrl);
    }
  } else {
    parts.push(row.pageUrl);
  }

  parts.push(bucketTimestamp(row.timestamp));
  return crypto.createHash("sha1").update(parts.join("|")).digest("hex");
}

export interface DedupCluster {
  kept: DedupRow;
  duplicates: DedupRow[];
}

export function clusterDuplicates(rows: DedupRow[]): DedupCluster[] {
  const groups = new Map<string, DedupRow[]>();
  for (const row of rows) {
    const key = computeDedupKey(row);
    const list = groups.get(key) ?? [];
    list.push(row);
    groups.set(key, list);
  }

  const clusters: DedupCluster[] = [];
  for (const [, group] of groups) {
    if (group.length <= 1) continue; // singleton — no duplicates

    // Sort by priority: highest method score → richest touchpoints → earliest timestamp
    group.sort((a, b) => {
      const methodDiff = methodScore(b.detectionMethod) - methodScore(a.detectionMethod);
      if (methodDiff !== 0) return methodDiff;
      const richDiff = touchpointRichness(b) - touchpointRichness(a);
      if (richDiff !== 0) return richDiff;
      return a.timestamp.getTime() - b.timestamp.getTime();
    });

    clusters.push({
      kept: group[0],
      duplicates: group.slice(1),
    });
  }

  return clusters;
}
