import { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
import multipart from "@fastify/multipart";
import { requirePermission } from "../../middleware/auth";
import { success, error, parseId } from "../../utils/response";
import { parseBody } from "../../schemas/common";
import {
  AiChatSchema,
  AiBudgetSchema,
  AiHistoryAppendSchema,
  AiCreateConversationSchema,
  AiRenameConversationSchema,
} from "../../schemas/ai.schema";
import { logAudit } from "../../services/audit";
import { config } from "../../config/env";
import {
  isConfigured,
  assertBudgetAvailable,
  getMonthSpendUsd,
  getBudgetUsd,
  setBudgetUsd,
  chat,
  extractInvoice,
  listConversations,
  createConversation,
  getConversationMessages,
  appendConversationMessages,
  renameConversation,
  deleteConversation,
  type ExtractedInvoice,
} from "../../services/ai.service";

export default async function aiRoutes(app: FastifyInstance): Promise<void> {
  await app.register(multipart, {
    // Cap files per request: the budget re-check already bounds *spend* mid-batch,
    // but this bounds the work (one vision call per file) of a single request.
    limits: { fileSize: config.nas.maxUploadSize, files: 20 },
  });

  // GET /api/admin/ai/usage — current-month spend + budget
  app.get(
    "/usage",
    { preHandler: requirePermission("ai.use") },
    async (_request: FastifyRequest, reply: FastifyReply) => {
      const [month_spend_usd, budget_usd] = await Promise.all([
        getMonthSpendUsd(),
        getBudgetUsd(),
      ]);
      return success(reply, {
        configured: isConfigured(),
        month_spend_usd: Math.round(month_spend_usd * 1_000_000) / 1_000_000,
        budget_usd,
        remaining_usd: Math.max(0, budget_usd - month_spend_usd),
      });
    },
  );

  // PUT /api/admin/ai/budget — set the monthly budget (admins have ai.use)
  app.put(
    "/budget",
    { preHandler: requirePermission("ai.use") },
    async (request: FastifyRequest, reply: FastifyReply) => {
      const body = parseBody(AiBudgetSchema, request.body);
      if ("error" in body) return error(reply, body.error, 400);
      const { budget_usd } = body.data;
      await setBudgetUsd(budget_usd);
      await logAudit({
        request,
        authData: request.authData,
        action: "update",
        entityType: "company_settings",
        description: `Změněn měsíční rozpočet AI na $${budget_usd}`,
        newValues: { ai_monthly_budget_usd: budget_usd },
      });
      return success(reply, { budget_usd }, 200, "Rozpočet uložen");
    },
  );

  // POST /api/admin/ai/chat
  app.post(
    "/chat",
    { preHandler: requirePermission("ai.use") },
    async (request: FastifyRequest, reply: FastifyReply) => {
      if (!isConfigured()) return error(reply, "AI není nakonfigurováno", 503);
      const body = parseBody(AiChatSchema, request.body);
      if ("error" in body) return error(reply, body.error, 400);
      const budgetErr = await assertBudgetAvailable();
      if (budgetErr) return error(reply, budgetErr.error, budgetErr.status);
      const { reply: text } = await chat(
        body.data.messages,
        request.authData!.userId,
      );
      const [budgetAfter, spendAfter] = await Promise.all([
        getBudgetUsd(),
        getMonthSpendUsd(),
      ]);
      const remaining_usd = Math.max(0, budgetAfter - spendAfter);
      return success(reply, { reply: text, remaining_usd });
    },
  );

  // POST /api/admin/ai/extract-invoices — multipart PDF files
  app.post(
    "/extract-invoices",
    { preHandler: requirePermission("ai.use") },
    async (request: FastifyRequest, reply: FastifyReply) => {
      if (!isConfigured()) return error(reply, "AI není nakonfigurováno", 503);
      const budgetErr = await assertBudgetAvailable();
      if (budgetErr) return error(reply, budgetErr.error, budgetErr.status);

      const parts = request.parts();
      const results: Array<{
        file_name: string;
        fields?: ExtractedInvoice;
        error?: string;
      }> = [];
      // True when the loop stops early because the budget was hit mid-batch —
      // surfaced to the client so it knows trailing files were NOT processed.
      let truncated = false;
      for await (const part of parts) {
        if (part.type !== "file") continue;
        const buf = await part.toBuffer();
        try {
          const fields = await extractInvoice(buf, request.authData!.userId);
          results.push({ file_name: part.filename || "faktura.pdf", fields });
        } catch (e) {
          request.log.error(e, "extractInvoice failed");
          results.push({
            file_name: part.filename || "faktura.pdf",
            error: "Nepodařilo se přečíst fakturu",
          });
        }
        // Re-check the budget between files so one batch can't blow far past it.
        const over = await assertBudgetAvailable();
        if (over) {
          truncated = true;
          break;
        }
      }
      if (results.length === 0)
        return error(reply, "Nebyl nahrán žádný soubor", 400);
      return success(reply, { invoices: results, truncated });
    },
  );

  // GET /conversations — this user's conversations (stable order)
  app.get(
    "/conversations",
    { preHandler: requirePermission("ai.use") },
    async (request: FastifyRequest, reply: FastifyReply) => {
      const conversations = await listConversations(request.authData!.userId);
      return success(reply, { conversations });
    },
  );

  // POST /conversations — create
  app.post(
    "/conversations",
    { preHandler: requirePermission("ai.use") },
    async (request: FastifyRequest, reply: FastifyReply) => {
      const body = parseBody(AiCreateConversationSchema, request.body);
      if ("error" in body) return error(reply, body.error, 400);
      const conversation = await createConversation(
        request.authData!.userId,
        body.data.title,
      );
      return success(reply, { conversation });
    },
  );

  // GET /conversations/:id/messages
  app.get(
    "/conversations/:id/messages",
    { preHandler: requirePermission("ai.use") },
    async (request: FastifyRequest, reply: FastifyReply) => {
      const id = parseId((request.params as { id: string }).id, reply);
      if (id === null) return;
      const result = await getConversationMessages(
        request.authData!.userId,
        id,
      );
      if ("error" in result) return error(reply, result.error, result.status);
      return success(reply, { messages: result.data });
    },
  );

  // POST /conversations/:id/messages — append turns
  app.post(
    "/conversations/:id/messages",
    { preHandler: requirePermission("ai.use") },
    async (request: FastifyRequest, reply: FastifyReply) => {
      const id = parseId((request.params as { id: string }).id, reply);
      if (id === null) return;
      const body = parseBody(AiHistoryAppendSchema, request.body);
      if ("error" in body) return error(reply, body.error, 400);
      const result = await appendConversationMessages(
        request.authData!.userId,
        id,
        body.data.messages,
      );
      if ("error" in result) return error(reply, result.error, result.status);
      return success(reply, result.data);
    },
  );

  // PATCH /conversations/:id — rename
  app.patch(
    "/conversations/:id",
    { preHandler: requirePermission("ai.use") },
    async (request: FastifyRequest, reply: FastifyReply) => {
      const id = parseId((request.params as { id: string }).id, reply);
      if (id === null) return;
      const body = parseBody(AiRenameConversationSchema, request.body);
      if ("error" in body) return error(reply, body.error, 400);
      const result = await renameConversation(
        request.authData!.userId,
        id,
        body.data.title,
      );
      if ("error" in result) return error(reply, result.error, result.status);
      return success(reply, { conversation: result.data });
    },
  );

  // DELETE /conversations/:id
  app.delete(
    "/conversations/:id",
    { preHandler: requirePermission("ai.use") },
    async (request: FastifyRequest, reply: FastifyReply) => {
      const id = parseId((request.params as { id: string }).id, reply);
      if (id === null) return;
      const result = await deleteConversation(request.authData!.userId, id);
      if ("error" in result) return error(reply, result.error, result.status);
      return success(reply, result.data);
    },
  );
}
