/**
 * GET /api/competitors/[id]/ranked-keywords?location=United+States[&limit=50]
 * ───────────────────────────────────────────────────────────────────────────
 * Returns the competitor's top ranking keywords (positions 1-20 by
 * default) sorted by search volume descending. Used by the "Top
 * Keywords (N ranking)" expandable panel inside each CompetitorCard.
 *
 * Cost: ~$0.05 per call (DataForSEO Ranked Keywords). Fetched on user
 * click only — never preloaded — so a brand with 10 competitors on
 * the page doesn't burn $0.50 just by loading.
 */

import { NextResponse } from "next/server";
import { getCurrentUser } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import { getRankedKeywords, isDataForSeoConfigured, recordCost } from "@/lib/services/dataforseo";

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

/** 7-day cache TTL for ranked-keywords responses. Same rationale as
 *  the keyword-overlap cache: DataForSEO's SERP index updates on a
 *  weekly cadence, each live call is ~$0.05, and repeat views of
 *  the same competitor shouldn't cost anything. */
const CACHE_TTL_MS = 7 * 86_400_000;
const CACHE_EVENT_TYPE = "competitor_ranked_keywords_cache";

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

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

  if (!isDataForSeoConfigured()) {
    return NextResponse.json({ error: "DataForSEO not configured", configured: false }, { status: 503 });
  }

  const { searchParams } = new URL(req.url);
  const location = searchParams.get("location") ?? "United States";
  const limit = Math.min(parseInt(searchParams.get("limit") ?? "50", 10), 200);
  // ?fresh=1 bypasses the 7-day cache so users can force a live
  // DataForSEO call when they know a competitor has recently gained
  // or lost rankings.
  const forceFresh = searchParams.get("fresh") === "1";

  console.info(`[ranked-keywords] request: competitorId=${params.id} domain=${competitor.domain} location=${location} limit=${limit}${forceFresh ? " (fresh)" : ""}`);

  // ── Cache read ────────────────────────────────────────────────────
  // Keyed by competitorId + location in eventData. Location matters
  // because DataForSEO returns different ranking sets per locale;
  // a UK query shouldn't short-circuit to a US-cached response.
  if (!forceFresh) {
    const cutoff = new Date(Date.now() - CACHE_TTL_MS);
    const cached = await prisma.siteEvent.findFirst({
      where: {
        brandId: competitor.brand.id,
        eventType: CACHE_EVENT_TYPE,
        timestamp: { gte: cutoff },
      },
      orderBy: { timestamp: "desc" },
      select: { eventData: true, timestamp: true },
    }).catch(() => null);
    if (cached) {
      const data = cached.eventData as { competitorId?: string; location?: string; payload?: unknown } | null;
      if (data?.competitorId === params.id && data?.location === location && data.payload) {
        const ageHours = Math.round((Date.now() - cached.timestamp.getTime()) / 3600_000);
        console.info(`[ranked-keywords] cache HIT (${ageHours}h old)`);
        return NextResponse.json({
          ...(data.payload as object),
          cached: true,
          cachedAgeHours: ageHours,
        });
      }
    }
    console.info(`[ranked-keywords] cache MISS — fetching fresh`);
  }

  // Cache-write helper. Fire-and-forget so a pool hiccup on the
  // write never breaks a successful read. Same sentinel pageUrl
  // convention as the other SiteEvent-backed caches in the app.
  const writeCache = <T extends object>(payload: T): T => {
    prisma.siteEvent.create({
      data: {
        brandId: competitor.brand.id,
        eventType: CACHE_EVENT_TYPE,
        pageUrl: `internal:ranked-keywords-cache`,
        eventData: {
          competitorId: params.id,
          location,
          payload,
          cachedAt: new Date().toISOString(),
        },
      },
    }).catch((err) => {
      console.warn("[ranked-keywords] cache write failed", err instanceof Error ? err.message : err);
    });
    return payload;
  };

  try {
    const result = await getRankedKeywords(competitor.domain, location, "English", limit, 20);
    if (!result) {
      // Null means DataForSEO isn't configured (guard above catches
      // this) or returned nothing parseable. Surface an empty result
      // with an explicit note so the UI doesn't sit on a loading
      // spinner waiting for data that won't come. NOT cached — a
      // future request after DataForSEO re-indexes the domain
      // should get through to the live call.
      console.warn(`[ranked-keywords] ${competitor.domain}: dataforseo returned null`);
      return NextResponse.json({
        keywords: [], totalCount: 0, costUsd: 0,
        note: "DataForSEO returned no data — domain may not be indexed.",
      });
    }
    recordCost(result.costUsd);
    console.info(`[dataforseo] ranked-keywords ${competitor.domain}: items=${result.keywords.length} total=${result.totalCount} cost=$${result.costUsd.toFixed(4)}`);
    if (result.totalCount === 0) {
      // Zero ranking keywords = legitimate empty set, not an error.
      // NOT cached for the same reason as the null path above.
      return NextResponse.json({
        ...result,
        note: "No keywords ranking in positions 1-20 for this domain.",
      });
    }
    // Happy path — persist for 7 days so repeat views of the same
    // competitor card don't re-trigger the ~$0.05 DataForSEO call.
    return NextResponse.json(writeCache(result));
  } catch (err) {
    const msg = err instanceof Error ? err.message : "DataForSEO request failed";
    console.error(`[ranked-keywords] failed for ${competitor.domain}:`, msg);
    return NextResponse.json({ error: msg }, { status: 500 });
  }
}
