import { useState, useRef, useEffect } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import apiFetch from "../../utils/api";
import { useAlert } from "../../context/AlertContext";
import { useAuth } from "../../context/AuthContext";
import {
  aiUsageOptions,
  aiConversationsOptions,
  aiConversationMessagesOptions,
  type ExtractedInvoice,
} from "../../lib/queries/ai";
import type {
  ChatTurn,
  ReviewInvoice,
  EditableField,
  StagedFile,
} from "./types";
import OdinSidebar from "./OdinSidebar";
import OdinThread from "./OdinThread";
import InvoiceReviewCard from "./InvoiceReviewCard";
import OdinComposer from "./OdinComposer";

// Stable, monotonic id (NOT crypto.randomUUID — undefined over plain HTTP).
let uidSeq = 0;
const nextUid = () => `ai-${(uidSeq += 1)}`;
const MODEL_CONTEXT = 20; // recent turns sent to the model
const MAX_STORE = 8000; // server caps a stored message at 8000 chars

const plural = (n: number) =>
  n === 1 ? "fakturu" : n >= 2 && n <= 4 ? "faktury" : "faktur";

function summaryNote(reviews: ReviewInvoice[]): string {
  if (reviews.length === 0) return "V přílohách jsem nenašel žádnou fakturu.";
  const suppliers = [
    ...new Set(reviews.map((r) => r.supplier_name).filter(Boolean)),
  ];
  const supTxt =
    suppliers.length === 1
      ? ` od ${suppliers[0]}`
      : suppliers.length > 1
        ? ` od ${suppliers[0]} a dalších`
        : "";
  const totals: Record<string, number> = {};
  for (const r of reviews) {
    const cur = r.currency || "CZK";
    totals[cur] = (totals[cur] || 0) + (Number(r.amount) || 0);
  }
  const totalsTxt = Object.entries(totals)
    .map(
      ([cur, sum]) =>
        `${sum.toLocaleString("cs-CZ", {
          minimumFractionDigits: 2,
          maximumFractionDigits: 2,
        })} ${cur}`,
    )
    .join(" + ");
  return `Načetl jsem ${reviews.length} ${plural(reviews.length)}${supTxt}, celkem ${totalsTxt}. Zkontrolujte údaje a uložte.`;
}

export default function OdinChat() {
  const alert = useAlert();
  const qc = useQueryClient();
  const { user, hasPermission } = useAuth();
  // Saving an extracted invoice hits /received-invoices (needs invoices.create);
  // Odin itself only needs ai.use, so the Save affordance is gated separately.
  const canSave = hasPermission("invoices.create");
  // Time-based Czech greeting + the user's first name (client-local time).
  const greetingHour = new Date().getHours();
  const greetingPhrase =
    greetingHour < 9
      ? "Dobré ráno"
      : greetingHour < 12
        ? "Dobrý den"
        : greetingHour < 18
          ? "Dobré odpoledne"
          : "Dobrý večer";
  const userName = (user?.fullName || user?.username || "")
    .trim()
    .split(/\s+/)[0];
  const { data: usage } = useQuery(aiUsageOptions());
  const { data: convData, isPending: convPending } = useQuery(
    aiConversationsOptions(),
  );
  const conversations = convData?.conversations ?? [];

  const [activeId, setActiveId] = useState<number | null>(null);
  const { data: messagesData } = useQuery({
    ...aiConversationMessagesOptions(activeId ?? 0),
    enabled: activeId != null,
  });

  const [input, setInput] = useState("");
  const [busy, setBusy] = useState(false);
  const [turns, setTurns] = useState<ChatTurn[]>([]);
  const [review, setReview] = useState<ReviewInvoice[]>([]);
  const [attachments, setAttachments] = useState<StagedFile[]>([]);
  const fileRef = useRef<HTMLInputElement>(null);
  const threadRef = useRef<HTMLDivElement>(null);
  const seededId = useRef<number | null>(null);

  // No auto-select: like claude.ai, we land on a fresh "new chat" (activeId
  // null) and the sidebar lists existing conversations to open. A conversation
  // row in the DB is created lazily on the first message (see submit), so
  // pressing "Nová konverzace" repeatedly never spawns empty conversations.

  // Seed the thread from the active conversation's messages, once per id.
  useEffect(() => {
    if (activeId != null && messagesData && seededId.current !== activeId) {
      seededId.current = activeId;
      setTurns(
        messagesData.messages.map((m) => ({
          role: m.role as "user" | "assistant",
          content: m.content,
        })),
      );
      setReview([]);
    }
  }, [activeId, messagesData]);

  // Keep the thread scrolled to the latest message.
  useEffect(() => {
    const el = threadRef.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, [turns, busy]);

  // AI is hidden entirely when the backend isn't configured.
  if (usage && usage.configured === false) return null;

  const messagesLoading =
    activeId != null && !messagesData && seededId.current !== activeId;

  const persist = (convId: number, msgs: ChatTurn[]) => {
    apiFetch(`/api/admin/ai/conversations/${convId}/messages`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        messages: msgs.map((m) => ({
          role: m.role,
          content: m.content.slice(0, MAX_STORE),
        })),
      }),
    })
      .then((r) => {
        if (!r.ok) return r.json().then((b) => Promise.reject(b?.error));
      })
      .catch((e) => console.error("[odin] history persist failed", e));
  };

  // Attaching only stages files; nothing is sent until Odeslat.
  const onFiles = (files: FileList | null) => {
    if (!files || files.length === 0) return;
    setAttachments((a) => [
      ...a,
      ...Array.from(files).map((file) => ({ id: nextUid(), file })),
    ]);
    if (fileRef.current) fileRef.current.value = "";
  };
  const removeAttachment = (id: string) =>
    setAttachments((a) => a.filter((s) => s.id !== id));

  const onSelect = (id: number) => {
    if (id === activeId || busy) return;
    setActiveId(id);
    setTurns([]);
    setReview([]);
    seededId.current = null;
  };

  // Returns the active conversation id, creating one if none is selected yet.
  const ensureActive = async (): Promise<number | null> => {
    if (activeId != null) return activeId;
    const res = await apiFetch("/api/admin/ai/conversations", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({}),
    });
    const b = await res.json();
    if (!res.ok || !b?.data?.conversation) return null;
    const id = b.data.conversation.id as number;
    seededId.current = id;
    setActiveId(id);
    qc.invalidateQueries({ queryKey: ["ai", "conversations"] });
    return id;
  };

  const canSubmit =
    !convPending && !busy && (!!input.trim() || attachments.length > 0);

  const submit = async () => {
    if (busy) return;
    const text = input.trim();
    const files = attachments.map((a) => a.file);
    if (!text && files.length === 0) return;

    setBusy(true);
    const prevTurns = turns;

    const userContent = [
      text,
      files.length > 0 ? `📎 ${files.map((f) => f.name).join(", ")}` : "",
    ]
      .filter(Boolean)
      .join("\n");
    const optimistic: ChatTurn[] = [
      ...turns,
      { role: "user", content: userContent },
    ];
    setTurns(optimistic);

    try {
      if (files.length > 0) {
        const fd = new FormData();
        files.forEach((f) => fd.append("files", f));
        const res = await apiFetch("/api/admin/ai/extract-invoices", {
          method: "POST",
          body: fd,
        });
        const body = await res.json();
        if (!res.ok) throw new Error(body?.error || "Chyba čtení faktur");
        const reviews: ReviewInvoice[] = (
          body.data.invoices as Array<{
            file_name: string;
            fields?: ExtractedInvoice;
            error?: string;
          }>
        )
          .map((r, i): ReviewInvoice | null => {
            const f = r.fields;
            return f
              ? {
                  uid: nextUid(),
                  supplier_name: f.supplier_name ?? "",
                  invoice_number: f.invoice_number ?? "",
                  amount: f.amount != null ? String(f.amount) : "",
                  currency: f.currency ?? "",
                  vat_rate: f.vat_rate != null ? String(f.vat_rate) : "",
                  issue_date: f.issue_date ?? "",
                  due_date: f.due_date ?? "",
                  description: f.description ?? "",
                  file_name: r.file_name,
                  file: files[i],
                }
              : null;
          })
          .filter((x): x is ReviewInvoice => x !== null);
        setReview((prev) => [...prev, ...reviews]);
        const note = summaryNote(reviews);
        setTurns((t) => [...t, { role: "assistant", content: note }]);
        setInput("");
        setAttachments([]);
        // Create the conversation now (first successful message) and persist.
        const convId = await ensureActive();
        if (convId != null)
          persist(convId, [
            { role: "user", content: userContent },
            { role: "assistant", content: note },
          ]);
        qc.invalidateQueries({ queryKey: ["ai", "usage"] });
      } else {
        let ctx = optimistic.slice(-MODEL_CONTEXT);
        while (ctx.length && ctx[0].role !== "user") ctx = ctx.slice(1);
        const res = await apiFetch("/api/admin/ai/chat", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ messages: ctx }),
        });
        const body = await res.json();
        if (!res.ok) throw new Error(body?.error || "Chyba AI");
        const reply: string = body.data.reply;
        setTurns((t) => [...t, { role: "assistant", content: reply }]);
        setInput("");
        // Create the conversation now (first successful message) and persist.
        const convId = await ensureActive();
        if (convId != null)
          persist(convId, [
            { role: "user", content: userContent },
            { role: "assistant", content: reply },
          ]);
        qc.invalidateQueries({ queryKey: ["ai", "usage"] });
      }
    } catch (e) {
      setTurns(prevTurns);
      const fallback = files.length > 0 ? "Chyba čtení faktur" : "Chyba AI";
      alert.error(e instanceof Error ? e.message : fallback);
    } finally {
      setBusy(false);
    }
  };

  const saveInvoice = async (inv: ReviewInvoice) => {
    if (busy) return;
    setBusy(true);
    try {
      const fd = new FormData();
      fd.append("files", inv.file, inv.file_name);
      fd.append(
        "invoices",
        JSON.stringify([
          {
            supplier_name: inv.supplier_name,
            invoice_number: inv.invoice_number || undefined,
            description: inv.description || undefined,
            amount: inv.amount === "" ? undefined : Number(inv.amount),
            currency: inv.currency,
            vat_rate: inv.vat_rate === "" ? undefined : Number(inv.vat_rate),
            issue_date: inv.issue_date || undefined,
            due_date: inv.due_date || undefined,
          },
        ]),
      );
      const res = await apiFetch("/api/admin/received-invoices", {
        method: "POST",
        body: fd,
      });
      const body = await res.json();
      if (!res.ok) throw new Error(body?.error || "Uložení selhalo");
      alert.success("Faktura uložena");
      setReview((r) => r.filter((x) => x.uid !== inv.uid));
      qc.invalidateQueries({ queryKey: ["invoices"] });
    } catch (e) {
      alert.error(e instanceof Error ? e.message : "Uložení selhalo");
    } finally {
      setBusy(false);
    }
  };

  // "Nová konverzace" just opens a fresh, empty composer — no DB row is created
  // until the first message, so repeated clicks never spawn empty conversations.
  const onNew = () => {
    if (busy) return;
    setActiveId(null);
    setTurns([]);
    setReview([]);
    setInput("");
    setAttachments([]);
    seededId.current = null;
  };

  const onRename = async (id: number, title: string) => {
    const res = await apiFetch(`/api/admin/ai/conversations/${id}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ title }),
    });
    if (res.ok) qc.invalidateQueries({ queryKey: ["ai", "conversations"] });
    else alert.error("Přejmenování selhalo");
  };

  const onDelete = async (id: number) => {
    const res = await apiFetch(`/api/admin/ai/conversations/${id}`, {
      method: "DELETE",
    });
    if (!res.ok) {
      alert.error("Smazání selhalo");
      return;
    }
    if (id === activeId) {
      // Deleting the open conversation returns to a fresh new chat.
      setActiveId(null);
      setTurns([]);
      setReview([]);
      seededId.current = null;
    }
    qc.invalidateQueries({ queryKey: ["ai", "conversations"] });
  };

  const patch = (uid: string, field: EditableField, value: string) =>
    setReview((r) =>
      r.map((inv) => (inv.uid === uid ? { ...inv, [field]: value } : inv)),
    );

  const activeTitle =
    conversations.find((c) => c.id === activeId)?.title ?? "Nová konverzace";

  return (
    <Box
      sx={{
        height: "calc(100dvh - 100px)",
        display: "flex",
        border: 1,
        borderColor: "divider",
        borderRadius: 3,
        bgcolor: "background.paper",
        overflow: "hidden",
      }}
    >
      <OdinSidebar
        conversations={conversations}
        activeId={activeId}
        busy={busy}
        onSelect={onSelect}
        onNew={onNew}
        onRename={onRename}
        onDelete={onDelete}
      />

      {/* Chat column */}
      <Box
        sx={{
          flex: 1,
          minWidth: 0,
          display: "flex",
          flexDirection: "column",
          gap: 1.5,
          p: 2,
        }}
      >
        <Box
          sx={{
            display: "flex",
            justifyContent: "space-between",
            alignItems: "center",
            gap: 1,
          }}
        >
          <Typography
            variant="subtitle1"
            noWrap
            sx={{ fontWeight: 600, minWidth: 0 }}
          >
            {activeTitle}
          </Typography>
          {usage && (
            <Typography
              variant="caption"
              color="text.secondary"
              sx={{ flexShrink: 0 }}
            >
              Utraceno: ${usage.month_spend_usd.toFixed(2)} / $
              {usage.budget_usd.toFixed(2)}
            </Typography>
          )}
        </Box>

        <OdinThread
          turns={turns}
          busy={busy}
          loading={messagesLoading}
          greetingPhrase={greetingPhrase}
          userName={userName}
          threadRef={threadRef}
        />

        {review.length > 0 && (
          <Box
            sx={{
              maxHeight: "35vh",
              overflowY: "auto",
              display: "flex",
              flexDirection: "column",
              gap: 1,
            }}
          >
            {review.map((inv) => (
              <InvoiceReviewCard
                key={inv.uid}
                inv={inv}
                busy={busy}
                canSave={canSave}
                onChange={patch}
                onSave={saveInvoice}
                onDiscard={(uid) =>
                  setReview((r) => r.filter((x) => x.uid !== uid))
                }
              />
            ))}
          </Box>
        )}

        <OdinComposer
          input={input}
          attachments={attachments}
          busy={busy}
          canSubmit={canSubmit}
          fileRef={fileRef}
          onInput={setInput}
          onFiles={onFiles}
          onRemoveAttachment={removeAttachment}
          onSubmit={submit}
        />
      </Box>
    </Box>
  );
}
