import { NextResponse } from "next/server";
import { z } from "zod";
import { getCurrentUser } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import { getDeadPages, createDeadPage, updateDeadPageStatus } from "@/lib/services/dead-pages";
import { validateQuery, validateBody } from "@/lib/validation/request";
import { BrandIdSchema } from "@/lib/validation/common-schemas";
import { withErrorHandler } from "@/lib/api/with-error-handler";

const QuerySchema = z.object({
  brandId: BrandIdSchema,
  type: z.string().default("404"),
  status: z.string().optional(),
  priority: z.string().optional(),
  sort: z.string().optional(),
});

const PostBodySchema = z.discriminatedUnion("action", [
  z.object({
    action: z.literal("create"),
    brandId: z.string(),
    deadUrl: z.string(),
    priority: z.string().optional(),
    suggestedFix: z.string().optional(),
    sources: z.array(z.object({ sourceUrl: z.string(), anchorText: z.string().optional() })),
  }),
  z.object({
    action: z.literal("update_status"),
    deadPageId: z.string(),
    status: z.string(),
    suggestedFix: z.string().optional(),
  }),
]);

/**
 * GET /api/dead-pages?brandId=xxx
 *
 *   - type=404    (default) — pages that return HTTP 404. Backed by the
 *                              DeadPage table populated by the site crawler.
 *   - type=broken            — live pages that contain links pointing at
 *                              known dead URLs. Sourced from SiteEvent
 *                              records the Site Tag writes.
 *   - type=orphan            — pages with no inbound internal links.
 *                              Computed from PageRecord.internalLinks.
 *
 * Optional filters for type=404: status, priority, sort.
 */
export const dynamic = "force-dynamic";

const BROKEN_LINK_LOOKBACK_MS = 30 * 24 * 60 * 60 * 1000;

interface BrokenEventData {
  destinationUrl?: string;
  linkText?: string;
  elementType?: string;
  selector?: string;
  parentSection?: string;
  statusCode?: number;
  source?: string;
}

export const GET = withErrorHandler(async (req: Request) => {
  const user = await getCurrentUser();
  if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });

  const { brandId, type, status, priority, sort } = validateQuery(req, QuerySchema);

  const brand = await prisma.brand.findUnique({
    where: { id: brandId },
    include: { organization: { include: { memberships: true } } },
  });
  if (!brand || !brand.organization.memberships.some((m) => m.userId === user.id)) {
    return NextResponse.json({ error: "Access denied" }, { status: 403 });
  }

  // ── type=broken — live pages linking to dead URLs ────────────────────────
  if (type === "broken") {
    const since = new Date(Date.now() - BROKEN_LINK_LOOKBACK_MS);
    const events = await prisma.siteEvent.findMany({
      where: {
        brandId,
        eventType: "broken_internal_link",
        timestamp: { gte: since },
      },
      orderBy: { timestamp: "desc" },
      take: 500,
    });

    // Flatten into row-per-broken-link for the table view. Each row gets
    // sourceUrl (where the link lives) plus the destination + link metadata
    // the Site Tag captured.
    const rows = events.map((ev) => {
      const data = (ev.eventData ?? {}) as BrokenEventData;
      return {
        sourceUrl: ev.pageUrl,
        destinationUrl: data.destinationUrl || "",
        linkText: data.linkText || "",
        elementType: data.elementType || "a",
        selector: data.selector || "",
        parentSection: data.parentSection || "body",
        statusCode: typeof data.statusCode === "number" ? data.statusCode : 404,
        detectedAt: ev.timestamp.toISOString(),
      };
    });

    // Also group by source page so the UI can render a "pages with broken
    // links" summary alongside the flat list.
    const groupedBySource: Record<string, typeof rows> = {};
    for (const row of rows) {
      const key = row.sourceUrl || "unknown";
      if (!groupedBySource[key]) groupedBySource[key] = [];
      groupedBySource[key].push(row);
    }

    return NextResponse.json({
      brokenLinks: rows,
      groupedBySource,
      count: rows.length,
      uniqueSources: Object.keys(groupedBySource).length,
    });
  }

  // ── type=orphan — pages with no inbound internal links ───────────────────
  if (type === "orphan") {
    // Pull all pages + their outbound link lists, then derive the set of
    // pages that never appear on anyone else's outbound list. Cheap to do
    // in memory since PageRecord rows are bounded per brand.
    const pages = await prisma.pageRecord.findMany({
      where: { brandId },
      select: {
        id: true,
        url: true,
        fullUrl: true,
        title: true,
        totalClicks: true,
        totalImpressions: true,
        totalVisits: true,
        currentStatus: true,
        internalLinks: true,
        isInNav: true,
      },
    });

    // Build the inbound-link set: every `href` referenced by some other
    // page's `internalLinks` array. Using a Set keeps the lookup O(1).
    const inbound = new Set<string>();
    for (const p of pages) {
      const links = p.internalLinks;
      if (!Array.isArray(links)) continue;
      for (const link of links as Array<{ href?: string }>) {
        if (link && typeof link.href === "string") inbound.add(link.href);
      }
    }

    // Homepage is never an orphan (nothing else links to it but every
    // site has it); skip it from the orphan list explicitly.
    const orphans = pages
      .filter((p) => p.currentStatus === 200)
      .filter((p) => p.url !== "/" && !inbound.has(p.url))
      .map((p) => ({
        url: p.url,
        fullUrl: p.fullUrl,
        title: p.title,
        gscClicks: p.totalClicks,
        gscImpressions: p.totalImpressions,
        visits: p.totalVisits,
        isInNav: p.isInNav,
        recommendation:
          p.totalClicks > 0
            ? "Page has GSC traffic but no internal links — add it to your nav or related content."
            : p.isInNav
            ? "Page is in navigation but no body content links to it — add contextual links."
            : "Low-traffic orphan — consider removing, merging, or linking from related pages.",
      }));

    return NextResponse.json({
      orphanPages: orphans,
      count: orphans.length,
      totalPages: pages.length,
    });
  }

  // ── type=404 (default) — dead pages from the crawler ─────────────────────
  const pages = await getDeadPages(brandId, { status, priority }, sort);

  return NextResponse.json(pages);
});

/**
 * POST /api/dead-pages
 */
export const POST = withErrorHandler(async (req: Request) => {
  const user = await getCurrentUser();
  if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });

  const body = await validateBody(req, PostBodySchema);

  if (body.action === "create") {
    const page = await createDeadPage(body);
    return NextResponse.json(page, { status: 201 });
  }

  const page = await updateDeadPageStatus(body.deadPageId, body.status, body.suggestedFix);
  return NextResponse.json(page);
});
