/**
 * Article star ratings — no-auth, one-vote-per-browser-session.
 *
 *   GET  /api/articles/rating?slug=<slug>
 *        → { avg: number|null, count: number, my_rating: number|null }
 *
 *   POST /api/articles/rating
 *        body { slug: string, rating: 1..5 }
 *        → { ok: true, avg, count, my_rating }
 *
 * Storage: distribute.article_ratings (centralized RMP cross-property table).
 * Each property writes with its own `property` value so the same table
 * powers BB / AV Beat / SNS / etc. — see the top-level constant below.
 *
 * Session: a uuid stored in the `av_rate_sid` cookie (1y) — the (property,
 * slug, session_id) tuple is UNIQUE so a duplicate POST from the same
 * browser session for the same article is a no-op. Clearing cookies /
 * opening incognito creates a new session, which is acceptable per the
 * "one per computer within a session" spec.
 */
import { NextRequest, NextResponse } from "next/server";
import { createAdminClient } from "@/lib/supabase/admin";
import { randomUUID, createHash } from "crypto";

export const dynamic = "force-dynamic";

const PROPERTY = "avbeat";
const SESSION_COOKIE = "av_rate_sid";
const SESSION_TTL_SECONDS = 60 * 60 * 24 * 365; // 1 year

function getOrMintSession(req: NextRequest): { sid: string; mint: boolean } {
  const existing = req.cookies.get(SESSION_COOKIE)?.value;
  if (existing && /^[0-9a-f-]{30,40}$/i.test(existing)) {
    return { sid: existing, mint: false };
  }
  return { sid: randomUUID(), mint: true };
}

function hashIp(req: NextRequest): string | null {
  const ip = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim()
          || req.headers.get("x-real-ip")
          || null;
  if (!ip) return null;
  // SHA-256 truncated — enough for soft analytics, not reversible
  return createHash("sha256").update(ip).digest("hex").slice(0, 32);
}

function attachSessionCookie(res: NextResponse, sid: string) {
  res.cookies.set(SESSION_COOKIE, sid, {
    maxAge: SESSION_TTL_SECONDS,
    sameSite: "lax",
    path: "/",
    httpOnly: false, // client JS may read it for optimistic UI
  });
}

async function loadSummary(slug: string, sid: string) {
  const sb = createAdminClient("distribute");
  const [agg, mine] = await Promise.all([
    sb.from("article_rating_aggregates")
      .select("real_count, real_avg")
      .eq("property", PROPERTY)
      .eq("article_slug", slug)
      .maybeSingle(),
    sb.from("article_ratings")
      .select("rating")
      .eq("property", PROPERTY)
      .eq("article_slug", slug)
      .eq("session_id", sid)
      .maybeSingle(),
  ]);
  return {
    avg: agg.data?.real_avg ?? null,
    count: agg.data?.real_count ?? 0,
    my_rating: mine.data?.rating ?? null,
  };
}

export async function GET(req: NextRequest) {
  const slug = new URL(req.url).searchParams.get("slug");
  if (!slug) {
    return NextResponse.json({ error: "slug required" }, { status: 400 });
  }
  const { sid, mint } = getOrMintSession(req);
  const summary = await loadSummary(slug, sid);
  const res = NextResponse.json(summary);
  if (mint) attachSessionCookie(res, sid);
  return res;
}

export async function POST(req: NextRequest) {
  let body: { slug?: unknown; rating?: unknown };
  try {
    body = await req.json();
  } catch {
    return NextResponse.json({ error: "invalid json" }, { status: 400 });
  }
  const slug = typeof body.slug === "string" ? body.slug : null;
  const rating = typeof body.rating === "number" ? Math.round(body.rating) : null;
  if (!slug || !rating || rating < 1 || rating > 5) {
    return NextResponse.json({ error: "slug + rating (1..5) required" }, { status: 400 });
  }

  const { sid, mint } = getOrMintSession(req);
  const sb = createAdminClient("distribute");

  // Insert; on conflict (property, slug, session_id) we silently no-op
  // because the spec says one rating per session. We then return the
  // CURRENT summary so the client renders what's actually stored.
  const { error } = await sb
    .from("article_ratings")
    .insert({
      property: PROPERTY,
      article_slug: slug,
      rating,
      session_id: sid,
      ip_hash: hashIp(req),
      user_agent: req.headers.get("user-agent")?.slice(0, 200) ?? null,
    });

  // 23505 = unique_violation — duplicate vote from same session, expected
  if (error && (error as { code?: string }).code !== "23505") {
    return NextResponse.json({ error: error.message }, { status: 500 });
  }

  const summary = await loadSummary(slug, sid);
  const res = NextResponse.json({ ok: true, ...summary });
  if (mint) attachSessionCookie(res, sid);
  return res;
}
