/**
 * Firebase Cloud Function: LLM Chat Proxy (streaming)
 *
 * Receives {message, history} from the Flutter app and streams the response
 * back as Server-Sent Events (SSE). The API key never leaves the server.
 *
 * Secrets required (set via `firebase functions:secrets:set`):
 *   - LLM_API_KEY: API key for OpenAI or Gemini
 *
 * Environment variables (set in functions/.env):
 *   - LLM_PROVIDER: "openai" (default) or "gemini"
 *   - LLM_SYSTEM_PROMPT: System prompt for the agent (optional)
 *
 * App dart-define:
 *   - LLM_CHAT_ENDPOINT: URL of this function after deploy
 *     Example: https://europe-west1-<project-id>.cloudfunctions.net/llmChat
 *
 * Deploy: kasy deploy  (or: firebase deploy --only functions:llmChat)
 */

import { onRequest } from "firebase-functions/v2/https";
import { defineSecret } from "firebase-functions/params";
import { getAuth } from "firebase-admin/auth";
import type { Request, Response } from "express";

const llmApiKey = defineSecret("LLM_API_KEY");

interface ChatMessage {
  role: "user" | "assistant";
  content: string;
}

interface RequestBody {
  message: string;
  history?: ChatMessage[];
}

// Pipes the OpenAI SSE stream directly to the Express response.
async function streamOpenAI(
  res: Response,
  apiKey: string,
  systemPrompt: string,
  message: string,
  history: ChatMessage[]
): Promise<void> {
  const messages: { role: string; content: string }[] = [];
  if (systemPrompt) messages.push({ role: "system", content: systemPrompt });
  for (const m of history) messages.push(m);
  messages.push({ role: "user", content: message });

  const upstream = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ model: "gpt-4o-mini", messages, stream: true }),
  });

  if (!upstream.ok) {
    throw new Error(`OpenAI ${upstream.status}: ${await upstream.text()}`);
  }

  const reader = upstream.body!.getReader();
  res.on("close", () => reader.cancel());
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    res.write(value);
  }
}

// Pipes the Gemini SSE stream directly to the Express response.
async function streamGemini(
  res: Response,
  apiKey: string,
  systemPrompt: string,
  message: string,
  history: ChatMessage[]
): Promise<void> {
  const contents = [
    ...history.map((m) => ({
      role: m.role === "assistant" ? "model" : "user",
      parts: [{ text: m.content }],
    })),
    { role: "user", parts: [{ text: message }] },
  ];

  const body: Record<string, unknown> = { contents };
  if (systemPrompt) {
    body.systemInstruction = { parts: [{ text: systemPrompt }] };
  }

  // alt=sse makes Gemini return the same SSE format as OpenAI
  const upstream = await fetch(
    `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:streamGenerateContent?key=${apiKey}&alt=sse`,
    {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(body),
    }
  );

  if (!upstream.ok) {
    throw new Error(`Gemini ${upstream.status}: ${await upstream.text()}`);
  }

  const reader = upstream.body!.getReader();
  res.on("close", () => reader.cancel());
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    res.write(value);
  }
}

async function verifyFirebaseToken(req: Request, res: Response): Promise<string | null> {
  const authHeader = req.headers.authorization ?? "";
  const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : "";
  if (!token) {
    res.status(401).json({ error: "Missing Authorization token" });
    return null;
  }
  try {
    const decoded = await getAuth().verifyIdToken(token);
    return decoded.uid;
  } catch {
    res.status(401).json({ error: "Invalid or expired token" });
    return null;
  }
}

export const llmChat = onRequest(
  { cors: true, secrets: [llmApiKey] },
  async (req, res) => {
    if (req.method !== "POST") {
      res.status(405).json({ error: "Method not allowed" });
      return;
    }

    const uid = await verifyFirebaseToken(req, res);
    if (!uid) return;

    const { message, history = [] }: RequestBody = req.body;
    if (!message) {
      res.status(400).json({ error: "Missing message" });
      return;
    }

    const apiKey = llmApiKey.value();
    if (!apiKey) {
      res
        .status(500)
        .json({ error: "LLM_API_KEY not configured. Run: firebase functions:secrets:set LLM_API_KEY" });
      return;
    }

    const provider = process.env.LLM_PROVIDER ?? "openai";
    const systemPrompt = process.env.LLM_SYSTEM_PROMPT ?? "";

    // SSE headers — must be set before any write
    res.setHeader("Content-Type", "text/event-stream");
    res.setHeader("Cache-Control", "no-cache");
    res.setHeader("X-Accel-Buffering", "no"); // disable nginx proxy buffering
    res.flushHeaders();

    try {
      if (provider === "gemini") {
        await streamGemini(res, apiKey, systemPrompt, message, history);
      } else {
        await streamOpenAI(res, apiKey, systemPrompt, message, history);
      }
      res.end();
    } catch (err) {
      console.error("[llm-chat]", err);
      // Send error as SSE event so the Flutter client can surface it
      res.write(`data: ${JSON.stringify({ error: "LLM request failed" })}\n\n`);
      res.end();
    }
  }
);
