/**
 * POST /api/ai/section
 *
 * Streaming "đọc lá số chuyên sâu" — one section at a time.
 *
 * Body shape:
 *   {
 *     input: ChartInput,
 *     section: "menh" | "tai-bach" | "quan-loc" | "phu-the" | "tu-tuc" | "dai-van",
 *     forDate?: "YYYY-MM-DD"   // only used for section="dai-van"
 *   }
 *
 * Returns a `text/event-stream` SSE response. Each event payload is JSON:
 *   { provider?: string, delta?: string, done?: true, error?: string }
 *
 * Why a separate route from /api/ai/chat: section reading uses a fixed
 * message construction (system + structured palace context), no free-form
 * question or chat history. Splitting keeps both routes simple and lets
 * us add per-section caching later without complicating the chat flow.
 */
import { NextRequest } from "next/server";
import { buildChart, type ChartInput } from "@/lib/tuvi/engine";
import { buildHoroscope } from "@/lib/tuvi/horoscope";
import { getProvider } from "@/lib/ai/provider";
import { buildSectionMessages, SECTIONS, type SectionKey } from "@/lib/ai/section-prompts";
import { currentUser } from "@/lib/charts";
import { prisma } from "@/lib/db";
import { resolvePlan, withinLimit } from "@/lib/billing/plans";
import { countAiSectionsLast24h, logAiSectionUsage } from "@/lib/billing/usage";
import { checkAnonSectionQuota, ANON_LIMITS } from "@/lib/rate-limit/anonymous";

export const runtime = "nodejs";

type Body = {
  input?: ChartInput;
  section?: SectionKey;
  forDate?: string;
};

const VALID_SECTIONS = new Set<SectionKey>(SECTIONS.map((s) => s.key));

function sseEvent(payload: Record<string, unknown>): string {
  return `data: ${JSON.stringify(payload)}\n\n`;
}

function jsonError(msg: string, status: number) {
  return new Response(JSON.stringify({ error: msg }), {
    status,
    headers: { "Content-Type": "application/json" },
  });
}

export async function POST(req: NextRequest) {
  let body: Body;
  try {
    body = (await req.json()) as Body;
  } catch {
    return jsonError("Invalid JSON body", 400);
  }

  const { input, section, forDate } = body;
  if (!input) return jsonError("Thiếu input lá số.", 400);
  if (!section || !VALID_SECTIONS.has(section)) {
    return jsonError("Thiếu hoặc sai mục đọc.", 400);
  }
  if (forDate && !/^\d{4}-\d{2}-\d{2}$/.test(forDate)) {
    return jsonError("forDate phải là YYYY-MM-DD.", 400);
  }

  let chart;
  try {
    chart = buildChart(input);
  } catch (e) {
    return jsonError(e instanceof Error ? e.message : "Không an được lá số.", 400);
  }

  // Quota gate. Logged-in users hit the per-user counter and FREE/PREMIUM
  // ceiling. Anonymous users hit a Redis-backed IP+cookie counter (3/24h)
  // to deter abuse without blocking honest first-time visitors.
  const me = await currentUser();
  let userPlanKey: "FREE" | "PREMIUM" | "ANONYMOUS" = "ANONYMOUS";
  if (me) {
    const u = await prisma.user.findUnique({
      where: { id: me.id },
      select: { plan: true },
    });
    userPlanKey = u?.plan === "PREMIUM" ? "PREMIUM" : "FREE";
    const plan = resolvePlan(userPlanKey);
    const used = await countAiSectionsLast24h(me.id);
    if (!withinLimit(used, plan.limits.aiSectionsPerDay)) {
      return jsonError(
        `Bạn đã dùng hết ${plan.limits.aiSectionsPerDay} lượt đọc AI hôm nay (gói ${plan.name}). Nâng cấp Premium để đọc không giới hạn.`,
        402
      );
    }
    // Best-effort log — don't fail the request if logging breaks.
    void logAiSectionUsage(me.id, section).catch(() => {});
  } else {
    // Anonymous quota via Redis (IP + cookie)
    const anonCheck = await checkAnonSectionQuota(req);
    if (!anonCheck.allowed && !anonCheck.failOpen) {
      return jsonError(
        `Bạn đã dùng hết ${ANON_LIMITS.aiSectionsPerDay} lượt đọc AI thử miễn phí trong 24h. Đăng ký tài khoản để có ${10} lượt mỗi ngày, hoặc nâng cấp Premium để đọc không giới hạn.`,
        402
      );
    }
  }

  // Only build horoscope when needed — it adds ~30ms iztro work + memory.
  let horoscope = undefined;
  if (section === "dai-van") {
    try {
      horoscope = buildHoroscope(input, forDate);
    } catch {
      horoscope = undefined;
    }
  }

  const messages = buildSectionMessages({ section, chart, horoscope });
  const provider = await getProvider();
  const encoder = new TextEncoder();

  const stream = new ReadableStream<Uint8Array>({
    async start(controller) {
      controller.enqueue(
        encoder.encode(sseEvent({ provider: provider.name, section, plan: userPlanKey }))
      );
      try {
        for await (const chunk of provider.streamMessages(messages)) {
          controller.enqueue(encoder.encode(sseEvent({ delta: chunk })));
        }
        controller.enqueue(encoder.encode(sseEvent({ done: true })));
      } catch (e) {
        const msg = e instanceof Error ? e.message : "AI provider failed";
        controller.enqueue(encoder.encode(sseEvent({ error: msg })));
      } finally {
        controller.close();
      }
    },
  });

  return new Response(stream, {
    headers: {
      "Content-Type": "text/event-stream; charset=utf-8",
      "Cache-Control": "no-cache, no-transform",
      Connection: "keep-alive",
      "X-Accel-Buffering": "no",
    },
  });
}
