/**
 * GET /api/rank-tracking/summary?brandId=X
 * ─────────────────────────────────────────
 * Dashboard-level aggregate: totals, position buckets, biggest movers,
 * 7-day deltas, and a single Visibility Score that summarises the
 * brand's tracked-keyword footprint as one number.
 *
 * The 7-day delta is computed by re-running the same bucket logic
 * against each keyword's last KeywordRanking row that pre-dates the
 * 7-day cutoff. Keywords created within the last 7 days contribute to
 * `keywordsAdded` but are excluded from position deltas (they have no
 * "previous" position to compare against).
 */

import { NextResponse } from "next/server";
import { getCurrentUser } from "@/lib/auth";
import { prisma } from "@/lib/prisma";

export const dynamic = "force-dynamic";

/** Visibility Score: sum of (1/position) across keywords with position 1-100,
 *  normalised so a perfect track (everything at #1) = 100 / total = 100 each.
 *  We report the average per keyword so the score stays comparable as the
 *  tracked set grows. */
function visibilityScore(positions: Array<number | null>): number {
  const ranked = positions.filter((p): p is number => p != null && p > 0 && p <= 100);
  if (ranked.length === 0) return 0;
  const sumInverse = ranked.reduce((s, p) => s + 1 / p, 0);
  // Average inverse position * 100 → 100 means everything at #1, ~33 means
  // average position ~3, ~10 means average position ~10.
  return Math.round((sumInverse / ranked.length) * 100);
}

/** Bucket counters for a set of position values. Used twice: once for
 *  current state, once for the snapshot 7 days ago to compute deltas. */
function bucketize(positions: Array<number | null>) {
  let top3 = 0, top10 = 0, top20 = 0, withPosition = 0, sum = 0, notRanking = 0;
  for (const pos of positions) {
    if (pos == null) { notRanking++; continue; }
    withPosition++;
    sum += pos;
    if (pos <= 3) top3++;
    if (pos <= 10) top10++;
    if (pos <= 20) top20++;
  }
  return {
    top3, top10, top20, notRanking, withPosition,
    averagePosition: withPosition > 0 ? Math.round((sum / withPosition) * 10) / 10 : null,
  };
}

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

  const { searchParams } = new URL(req.url);
  const brandId = searchParams.get("brandId");
  if (!brandId) return NextResponse.json({ error: "brandId required" }, { status: 400 });

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

  const sevenDaysAgo = new Date(Date.now() - 7 * 86_400_000);

  // ── Active keywords with latest ranking + the latest ranking from
  //    >7 days ago. Two takes inside `include` so we get both in a
  //    single query per keyword. ──────────────────────────────────────
  const keywords = await prisma.trackedKeyword.findMany({
    where: { brandId, isActive: true },
    include: {
      rankings: { orderBy: { checkedAt: "desc" }, take: 1 },
    },
  });

  // Pull the "snapshot 7 days ago" position for each keyword in a single
  // batched query. groupBy can't return the row itself so we use a raw
  // findMany with a subquery-style approach: get the most recent ranking
  // per keyword that's older than the cutoff.
  const snapshotRows = await prisma.keywordRanking.findMany({
    where: {
      trackedKeywordId: { in: keywords.map((k) => k.id) },
      checkedAt: { lte: sevenDaysAgo },
    },
    orderBy: [{ trackedKeywordId: "asc" }, { checkedAt: "desc" }],
    select: { trackedKeywordId: true, position: true, checkedAt: true },
  });
  const snapshotMap = new Map<string, number | null>();
  for (const r of snapshotRows) {
    if (!snapshotMap.has(r.trackedKeywordId)) {
      snapshotMap.set(r.trackedKeywordId, r.position);
    }
  }

  const total = keywords.length;
  const currentPositions: Array<number | null> = [];
  const snapshotPositions: Array<number | null> = [];

  let keywordsAddedThisWeek = 0;
  const movers: Array<{ keyword: string; change: number; position: number | null }> = [];

  for (const kw of keywords) {
    const latest = kw.rankings[0];
    const pos = latest?.position ?? null;
    const prev = latest?.previousPosition ?? null;
    currentPositions.push(pos);

    // Snapshot for delta math: prefer the 7-day-ago snapshot. If the
    // keyword didn't exist 7 days ago, skip from snapshot but count it
    // toward `keywordsAdded`.
    if (kw.createdAt >= sevenDaysAgo) {
      keywordsAddedThisWeek++;
    } else {
      snapshotPositions.push(snapshotMap.get(kw.id) ?? null);
    }

    if (pos != null && prev != null) {
      movers.push({ keyword: kw.keyword, change: prev - pos, position: pos });
    }
  }

  const current = bucketize(currentPositions);
  const snapshot = bucketize(snapshotPositions);

  // Sort movers descending by absolute change
  movers.sort((a, b) => Math.abs(b.change) - Math.abs(a.change));
  const improved = movers.filter((m) => m.change > 0).slice(0, 3);
  const declined = movers.filter((m) => m.change < 0).slice(0, 3);

  // Distribution buckets for chart (4-10 / 11-20 / 21-30 derived from
  // cumulative top-N counts so a keyword never gets double-counted).
  const distribution = [
    { label: "1–3", count: current.top3 },
    { label: "4–10", count: current.top10 - current.top3 },
    { label: "11–20", count: current.top20 - current.top10 },
    { label: "21–30", count: current.withPosition - current.top20 },
    { label: "Not ranking", count: current.notRanking },
  ];

  const visScoreNow = visibilityScore(currentPositions);
  const visScorePrev = visibilityScore(snapshotPositions);

  return NextResponse.json({
    total,
    averagePosition: current.averagePosition,
    top3: current.top3,
    top10: current.top10,
    top20: current.top20,
    notRanking: current.notRanking,
    visibilityScore: visScoreNow,
    improved,
    declined,
    distribution,
    deltas: {
      // Negative averagePosition delta is "good" (lower position number =
      // better rank). The UI flips the colour accordingly.
      averagePosition: current.averagePosition != null && snapshot.averagePosition != null
        ? Math.round((current.averagePosition - snapshot.averagePosition) * 10) / 10
        : null,
      top3: current.top3 - snapshot.top3,
      top10: current.top10 - snapshot.top10,
      notRanking: current.notRanking - snapshot.notRanking,
      visibilityScore: visScoreNow - visScorePrev,
      keywordsAdded: keywordsAddedThisWeek,
    },
  });
}
