/**
 * POST /api/site-tag/nav-structure
 *
 * Site Tag reports the links it found inside <header>, <nav>, and
 * common nav containers. We diff this against the most recent
 * NavSnapshot for the brand and, for every newly-added link, set
 * PageRecord.isInNav = true + addedToNavAt = now. For every removed
 * link, mark the PageRecord as no longer in nav.
 *
 * Payload:
 *   { siteId, sourceUrl, links: [{ href, anchorText, section }] }
 */

import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import {
  getCorsHeaders,
  resolveSiteFromTag,
  toPathKey,
  diffNavSnapshots,
  type NavLinkInput,
} from "@/lib/services/page-lifecycle";

export const dynamic = "force-dynamic";

// Keep only the most recent N snapshots per brand to bound storage
const MAX_NAV_SNAPSHOTS_PER_BRAND = 30;

export async function OPTIONS(req: Request) {
  return new NextResponse(null, { status: 204, headers: getCorsHeaders(req) });
}

interface NavBody {
  siteId?: string;
  sourceUrl?: string;
  links?: Array<{ href?: string; anchorText?: string; section?: string }>;
}

export async function POST(req: Request) {
  const cors = getCorsHeaders(req);
  let body: NavBody;
  try {
    body = (await req.json()) as NavBody;
  } catch {
    return NextResponse.json({ ok: false, error: "Invalid JSON" }, { status: 400, headers: cors });
  }

  const resolved = await resolveSiteFromTag(body.siteId, body.sourceUrl || "");
  if (!resolved.ok) {
    return NextResponse.json(
      { ok: false, error: resolved.error },
      { status: resolved.status, headers: cors },
    );
  }
  const brandId = resolved.site.brandId;

  // Normalise incoming nav links (internal only, deduped by path)
  const allowed = resolved.site.allowedDomains.map((d) => d.toLowerCase());
  const seen = new Set<string>();
  const current: NavLinkInput[] = [];
  for (const raw of body.links ?? []) {
    if (!raw || typeof raw.href !== "string") continue;
    const href = raw.href.trim();
    if (!href || href.startsWith("#")) continue;

    let pathOnly: string | null = null;
    if (/^https?:\/\//i.test(href)) {
      try {
        const u = new URL(href);
        const host = u.hostname.toLowerCase();
        if (!allowed.some((d) => host === d || host.endsWith(`.${d}`))) continue;
        pathOnly = toPathKey(u.pathname);
      } catch { continue; }
    } else {
      pathOnly = toPathKey(href);
    }
    if (!pathOnly || seen.has(pathOnly)) continue;
    seen.add(pathOnly);
    current.push({
      href: pathOnly,
      anchorText: typeof raw.anchorText === "string" ? raw.anchorText.trim().slice(0, 200) : "",
      section: typeof raw.section === "string" ? raw.section.trim().slice(0, 50) : null,
    });
  }

  // Load the most recent snapshot to diff against
  const prevSnapshot = await prisma.navSnapshot.findFirst({
    where: { brandId },
    orderBy: { capturedAt: "desc" },
    select: { links: true },
  });
  const previousLinks: NavLinkInput[] | null = Array.isArray(prevSnapshot?.links)
    ? (prevSnapshot!.links as unknown as NavLinkInput[])
    : null;

  const diff = diffNavSnapshots(previousLinks, current);

  // Store the new snapshot (only if something actually changed, or this is
  // the first snapshot ever — avoids flooding the table).
  if (!previousLinks || diff.added.length > 0 || diff.removed.length > 0) {
    await prisma.navSnapshot.create({
      data: {
        brandId,
        links: current as unknown as object,
      },
    });

    // Trim old snapshots past the retention cap
    const total = await prisma.navSnapshot.count({ where: { brandId } });
    if (total > MAX_NAV_SNAPSHOTS_PER_BRAND) {
      const oldSnapshots = await prisma.navSnapshot.findMany({
        where: { brandId },
        orderBy: { capturedAt: "desc" },
        skip: MAX_NAV_SNAPSHOTS_PER_BRAND,
        select: { id: true },
      });
      if (oldSnapshots.length) {
        await prisma.navSnapshot.deleteMany({
          where: { id: { in: oldSnapshots.map((s) => s.id) } },
        });
      }
    }
  }

  // Apply diff to PageRecord flags
  const now = new Date();
  if (diff.added.length) {
    for (const link of diff.added) {
      const existing = await prisma.pageRecord.findUnique({
        where: { brandId_url: { brandId, url: link.href } },
        select: { id: true, addedToNavAt: true },
      });
      if (existing) {
        await prisma.pageRecord.update({
          where: { id: existing.id },
          data: {
            isInNav: true,
            addedToNavAt: existing.addedToNavAt ?? now,
            removedFromNavAt: null,
          },
        });
      } else {
        // Nav contains a page we've never seen — create a stub record so
        // the "new page in nav" event doesn't get lost.
        await prisma.pageRecord.create({
          data: {
            brandId,
            url: link.href,
            fullUrl: link.href,
            title: link.anchorText || null,
            isInNav: true,
            addedToNavAt: now,
          },
        });
      }
    }
  }

  if (diff.removed.length) {
    await prisma.pageRecord.updateMany({
      where: { brandId, url: { in: diff.removed.map((l) => l.href) } },
      data: { isInNav: false, removedFromNavAt: now },
    });
  }

  return NextResponse.json(
    {
      ok: true,
      total: current.length,
      added: diff.added.length,
      removed: diff.removed.length,
    },
    { headers: cors },
  );
}
