/**
 * POST /api/admin/debug/dedup-conversions
 *
 * Retroactive dedup for SiteConversion rows that overlap within a
 * 60-second window for the same (brandId, sessionId, pageUrl,
 * canonicalType). The live resolver (conversion-resolver.ts) has
 * been dedup'ing new writes since commit fa7d14a — this endpoint
 * cleans up the BACKLOG of duplicates written before that.
 *
 * What it does:
 *   1. Finds duplicate groups: multiple SiteConversion rows for the
 *      same (brandId, sessionId, pageUrl, canonicalType) within a
 *      60-second window. Canonical type is resolved via
 *      canonicalConversionType so "phone_call" /
 *      "phone_number_click" / "click_to_call" group together.
 *   2. For each group: KEEPS the earliest row, updates its
 *      formFields._interaction_count to the group size, stamps
 *      _deduped_at + _deduped_from_count for audit. Deletes the
 *      rest.
 *
 * Returns (dry-run + apply share the shape):
 *   {
 *     dryRun, scope,
 *     groups:                   N,   // how many dup groups detected
 *     duplicatesDeleted:        N,   // rows deleted (apply) or would-delete (dry-run)
 *     interactionCountsUpdated: N,   // kept rows that got interaction_count stamped
 *     sampleGroups: [
 *       { canonicalType, pageUrl, sessionId, count }
 *     ],
 *     hint: human-readable summary
 *   }
 *
 * Body (all optional):
 *   { brandId?, dryRun? (default true), limit? (default 5000, max 50000) }
 *
 * Lives under /api/admin/* so the admin-host middleware lets it
 * through. Superadmin-gated. POST-only so a logged-out browser
 * can't trigger it via prefetch.
 */

import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { requireSuperadmin, withAdminTiming } from "@/lib/admin-auth";
import { canonicalConversionType } from "@/lib/conversion-taxonomy";

export const dynamic = "force-dynamic";
export const maxDuration = 60;

const DEDUP_WINDOW_MS = 60_000;

interface DedupBody {
  brandId?: string;
  dryRun?: boolean;
  limit?: number;
}

export async function POST(req: Request) {
  const denied = await requireSuperadmin();
  if (denied) return denied;

  const body = (await req.json().catch(() => ({}))) as DedupBody;
  const dryRun = body.dryRun !== false;          // default TRUE for safety
  const limit  = Math.max(1, Math.min(50_000, body.limit ?? 5_000));
  const brandId = body.brandId ?? null;

  return NextResponse.json(await withAdminTiming("debug.dedup-conversions", async () => {
    // Pull the candidate rows, ordered by time so the "keep earliest"
    // rule collapses on a single linear pass.
    // intentionally unfiltered: admin-only endpoint; needs raw or all-time counts
    const rows = await prisma.siteConversion.findMany({
      where: { ...(brandId ? { brandId } : {}) },
      select: {
        id: true, brandId: true, sessionId: true, pageUrl: true,
        conversionType: true, timestamp: true, formFields: true,
      },
      orderBy: { timestamp: "asc" },
      take: limit,
    });

    // Group by (brandId, sessionId, canonicalType, pageUrl) and split
    // into 60-second windows. Key = first-row-timestamp in the window.
    // Within each group we keep the first row and mark the rest for
    // deletion. Uses the fact that `rows` is time-sorted above.
    type Group = {
      brandId: string;
      sessionId: string;
      pageUrl: string;
      canonicalType: string;
      leaderId: string;
      leaderTimestamp: Date;
      leaderFormFields: unknown;
      members: string[]; // ids in the window (excluding leader)
      sampleOriginalType: string;
    };
    const groupKey = new Map<string, Group>();
    const identity = (r: typeof rows[number], canonical: string) =>
      `${r.brandId}|${r.sessionId}|${r.pageUrl}|${canonical}`;

    for (const r of rows) {
      const canon = canonicalConversionType(r.conversionType) ?? r.conversionType;
      const k = identity(r, canon);
      const existing = groupKey.get(k);
      if (!existing) {
        groupKey.set(k, {
          brandId: r.brandId,
          sessionId: r.sessionId,
          pageUrl: r.pageUrl,
          canonicalType: canon,
          leaderId: r.id,
          leaderTimestamp: r.timestamp,
          leaderFormFields: r.formFields,
          members: [],
          sampleOriginalType: r.conversionType,
        });
        continue;
      }
      const within = r.timestamp.getTime() - existing.leaderTimestamp.getTime() <= DEDUP_WINDOW_MS;
      if (within) {
        existing.members.push(r.id);
      } else {
        // New window — this row becomes the leader under the same
        // identity key.
        groupKey.set(k, {
          brandId: r.brandId,
          sessionId: r.sessionId,
          pageUrl: r.pageUrl,
          canonicalType: canon,
          leaderId: r.id,
          leaderTimestamp: r.timestamp,
          leaderFormFields: r.formFields,
          members: [],
          sampleOriginalType: r.conversionType,
        });
      }
    }

    // Filter to groups with ≥1 duplicate.
    const dupGroups = Array.from(groupKey.values()).filter((g) => g.members.length > 0);

    const duplicatesDeleted = dupGroups.reduce((sum, g) => sum + g.members.length, 0);
    const interactionCountsUpdated = dupGroups.length;
    const sampleGroups = dupGroups.slice(0, 20).map((g) => ({
      canonicalType: g.canonicalType,
      sampleOriginalType: g.sampleOriginalType,
      pageUrl: g.pageUrl.slice(0, 120),
      sessionId: g.sessionId.slice(0, 60),
      count: g.members.length + 1,
    }));

    if (!dryRun) {
      // Apply: for each group, stamp the leader with the interaction
      // count + delete the members. Sequential loop — the dedup pass
      // is small (dup groups are a long-tail minority of total rows);
      // parallelising would pile on Postgres connections for little
      // wall-clock win.
      for (const g of dupGroups) {
        try {
          const existingMeta = (g.leaderFormFields ?? {}) as Record<string, unknown>;
          const newMeta = {
            ...existingMeta,
            _interaction_count: g.members.length + 1,
            _deduped_at: new Date().toISOString(),
            _deduped_from_count: g.members.length + 1,
          };
          await prisma.siteConversion.update({
            where: { id: g.leaderId },
            data: { formFields: JSON.parse(JSON.stringify(newMeta)) },
          });
          if (g.members.length > 0) {
            await prisma.siteConversion.deleteMany({
              where: { id: { in: g.members } },
            });
          }
        } catch (err) {
          console.warn("[dedup-conversions] group update/delete failed", {
            leaderId: g.leaderId,
            err: err instanceof Error ? err.message : "unknown",
          });
        }
      }
    }

    const result = {
      dryRun,
      scope: brandId ?? "all",
      scanned: rows.length,
      groups: dupGroups.length,
      duplicatesDeleted,
      interactionCountsUpdated,
      sampleGroups,
      hint: dryRun
        ? `Dry run — found ${dupGroups.length} duplicate groups covering ${duplicatesDeleted} duplicate rows. Pass { dryRun: false } to apply.`
        : `Applied: merged ${dupGroups.length} groups, deleted ${duplicatesDeleted} duplicate rows, stamped interaction counts on ${interactionCountsUpdated} kept rows.`,
    };

    console.info("[dedup-conversions]", result);
    return result;
  }));
}
