/**
 * Anonymous AI quota — IP + cookie UUID combined.
 *
 * Strategy: hash(IP + cookieUUID) → 24h sliding counter in Redis.
 *
 * Why both: NAT (company/school/mobile carrier) shares IP across many users —
 * cookie alone defeats incognito reset; IP alone defeats cookie clear; together
 * the user has to reset BOTH (clear cookie + change network) to get a fresh
 * counter. Friction high enough to deter casual abuse, low enough not to
 * frustrate genuine first-time visitors.
 *
 * Limits live here as constants — single source of truth for anonymous tier.
 */

import { createHash, randomUUID } from "node:crypto";
import type { NextRequest } from "next/server";
import { cookies } from "next/headers";
import { checkAndIncrement, type RateCheck } from "./redis";

const ANON_COOKIE = "tv_anon_id";
const ANON_COOKIE_MAX_AGE_SEC = 60 * 60 * 24 * 30; // 30 days

/**
 * Anonymous quota windows. AI section reads cost more (longer streams), so
 * we cap them tighter than chat.
 */
export const ANON_LIMITS = {
  /** Max AI section reads per anonymous identity per 24h */
  aiSectionsPerDay: 3,
  /** Max chat messages per anonymous identity per 24h (currently unused —
   *  /api/charts/[id]/qa requires login. Reserved for future free chat) */
  chatMessagesPerDay: 5,
} as const;

const WINDOW_SEC = 60 * 60 * 24;

/**
 * Pull a "best effort" client IP from request headers.
 * Order: x-forwarded-for (NPM), x-real-ip, fallback "unknown".
 */
export function getClientIP(req: NextRequest): string {
  const xff = req.headers.get("x-forwarded-for");
  if (xff) {
    const first = xff.split(",")[0]?.trim();
    if (first) return first;
  }
  const real = req.headers.get("x-real-ip");
  if (real) return real.trim();
  return "unknown";
}

/**
 * Read or mint the anonymous cookie UUID.
 * MUST be called from a route handler / server action / server component.
 */
export async function getOrCreateAnonId(): Promise<string> {
  const c = await cookies();
  const existing = c.get(ANON_COOKIE)?.value;
  if (existing && existing.length >= 16) return existing;
  const fresh = randomUUID();
  c.set(ANON_COOKIE, fresh, {
    httpOnly: true,
    sameSite: "lax",
    secure: process.env.NODE_ENV === "production",
    maxAge: ANON_COOKIE_MAX_AGE_SEC,
    path: "/",
  });
  return fresh;
}

/**
 * Build a stable identity key combining IP + anon cookie.
 * SHA-256 to keep PII out of Redis.
 */
function identityKey(scope: string, ip: string, anonId: string): string {
  const raw = `${ip}|${anonId}`;
  const hash = createHash("sha256").update(raw).digest("hex").slice(0, 24);
  return `tv:rl:${scope}:${hash}`;
}

/**
 * Check anonymous AI section quota.
 * Increments counter — caller should check `result.allowed` BEFORE proceeding.
 */
export async function checkAnonSectionQuota(
  req: NextRequest
): Promise<RateCheck & { identity: string }> {
  const ip = getClientIP(req);
  const anonId = await getOrCreateAnonId();
  const key = identityKey("ai-section", ip, anonId);
  const result = await checkAndIncrement(key, ANON_LIMITS.aiSectionsPerDay, WINDOW_SEC);
  return { ...result, identity: `${ip.slice(0, 12)}…` };
}

/**
 * Generic quota check for arbitrary scopes (palmistry-vision, etc).
 *
 * Use this for endpoints that need their own quota bucket separate from
 * AI section. Pass `userId` for logged-in users (skip cookie path) so the
 * counter follows the user across browsers, or omit to fall back to
 * IP+cookie identity.
 */
export async function checkScopedQuota(
  req: NextRequest,
  scope: string,
  limit: number,
  opts: { userId?: string; windowSec?: number } = {}
): Promise<RateCheck & { identity: string }> {
  const window = opts.windowSec ?? WINDOW_SEC;
  let key: string;
  let identity: string;
  if (opts.userId) {
    key = `tv:rl:${scope}:user:${opts.userId}`;
    identity = `user:${opts.userId.slice(0, 8)}`;
  } else {
    const ip = getClientIP(req);
    const anonId = await getOrCreateAnonId();
    key = identityKey(scope, ip, anonId);
    identity = `${ip.slice(0, 12)}…`;
  }
  const result = await checkAndIncrement(key, limit, window);
  return { ...result, identity };
}
