import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
  type ReactNode,
} from "react";
import { toast } from "sonner";
import {
  answerQueryApi,
  approveInvoiceApi,
  approveQueryApi,
  bulkApproveApi,
  bulkNeedsReviewApi,
  checkApiHealth,
  createQueryApi,
  fetchLedgerState,
  isApiEnabled,
  rejectInvoiceApi,
  setClientRuleApi,
  setInvoiceStatusApi,
  setTxStatusApi,
  syncInvoiceApi,
  updateInvoiceApi,
  updateSettingsApi,
  updateSuggestionApi,
  uploadDocumentApi,
} from "./api";
import { ACTIVITY, BANK_TX, CLIENTS, INVOICES, QUERIES, SETTINGS } from "./mock-data";
import type {
  ActivityEvent,
  ActivityType,
  BankTx,
  Client,
  ClientQuery,
  Invoice,
  InvoiceStatus,
  Settings,
  Suggestion,
} from "./ledger-types";

export const CLIENT_PORTAL_CLIENT_ID = "c1";

interface Ctx {
  ready: boolean;
  usingApi: boolean;
  clients: Client[];
  invoices: Invoice[];
  transactions: BankTx[];
  queries: ClientQuery[];
  activity: ActivityEvent[];
  settings: Settings;
  clientName: (id: string) => string;
  getInvoice: (id: string) => Invoice | undefined;
  uploadDocument: (clientId: string, fileName: string, actor: string) => string;
  updateInvoiceFields: (id: string, patch: Partial<Invoice>) => void;
  updateSuggestion: (id: string, patch: Partial<Suggestion>) => void;
  setInvoiceStatus: (id: string, status: InvoiceStatus) => void;
  approveInvoice: (id: string) => void;
  rejectInvoice: (id: string) => void;
  syncInvoice: (id: string) => void;
  bulkApprove: (ids: string[]) => void;
  bulkNeedsReview: (ids: string[]) => void;
  createQuery: (clientId: string, question: string, invoiceId?: string) => void;
  answerQuery: (id: string, answer: string, proof?: string) => void;
  approveQuery: (id: string) => void;
  setTxStatus: (id: string, status: BankTx["status"]) => void;
  updateSettings: (patch: Partial<Settings>) => void;
  setClientRule: (clientId: string, rule: string) => void;
}

const LedgerContext = createContext<Ctx | null>(null);

let seq = 1000;
const nextId = (prefix: string) => `${prefix}-${++seq}`;

const MOCK_SUPPLIERS = [
  { supplier: "Horeca Groothandel Sligro", ledger: "Cost of goods", conf: 91 },
  { supplier: "PostNL Zakelijk", ledger: "Shipping costs", conf: 72 },
  { supplier: "KPN Zakelijk", ledger: "Telecom & internet", conf: 88 },
];

export function LedgerProvider({ children }: { children: ReactNode }) {
  const [ready, setReady] = useState(!isApiEnabled());
  const [usingApi, setUsingApi] = useState(false);
  const [clients, setClients] = useState<Client[]>(CLIENTS);
  const [invoices, setInvoices] = useState<Invoice[]>(INVOICES);
  const [transactions, setTransactions] = useState<BankTx[]>(BANK_TX);
  const [queries, setQueries] = useState<ClientQuery[]>(QUERIES);
  const [activity, setActivity] = useState<ActivityEvent[]>(ACTIVITY);
  const [settings, setSettings] = useState<Settings>(SETTINGS);

  const applyState = useCallback(
    (state: {
      clients: Client[];
      invoices: Invoice[];
      transactions: BankTx[];
      queries: ClientQuery[];
      activity: ActivityEvent[];
      settings: Settings;
    }) => {
      setClients(state.clients);
      setInvoices(state.invoices);
      setTransactions(state.transactions);
      setQueries(state.queries);
      setActivity(state.activity);
      setSettings(state.settings);
    },
    [],
  );

  const refreshState = useCallback(async () => {
    const state = await fetchLedgerState();
    applyState(state);
  }, [applyState]);

  useEffect(() => {
    if (!isApiEnabled()) {
      setReady(true);
      return;
    }

    let cancelled = false;

    (async () => {
      const healthy = await checkApiHealth();
      if (cancelled) return;

      if (!healthy) {
        console.warn("Backend API unavailable — using local mock data.");
        setUsingApi(false);
        setReady(true);
        return;
      }

      try {
        const state = await fetchLedgerState();
        if (cancelled) return;
        applyState(state);
        setUsingApi(true);
      } catch (error) {
        console.warn("Failed to load backend state — using local mock data.", error);
        setUsingApi(false);
      } finally {
        if (!cancelled) setReady(true);
      }
    })();

    return () => {
      cancelled = true;
    };
  }, [applyState]);

  const clientName = useCallback(
    (id: string) => clients.find((c) => c.id === id)?.name ?? "Unknown client",
    [clients],
  );

  const log = useCallback(
    (type: ActivityType, text: string, actor: string, clientId?: string, invoiceId?: string) => {
      setActivity((prev) => [
        { id: nextId("act"), type, text, actor, at: Date.now(), clientId, invoiceId },
        ...prev,
      ]);
    },
    [],
  );

  const patchInvoice = useCallback((id: string, patch: Partial<Invoice>) => {
    setInvoices((prev) => prev.map((i) => (i.id === id ? { ...i, ...patch } : i)));
  }, []);

  const uploadDocument = useCallback(
    (clientId: string, fileName: string, actor: string) => {
      if (usingApi) {
        void (async () => {
          try {
            const invoice = await uploadDocumentApi(clientId, fileName, actor);
            await refreshState();
            toast.success("Document uploaded", { description: fileName });
            toast.success("AI processing complete", {
              description: `${invoice.supplier} · ${invoice.confidence}% confidence`,
            });
          } catch (error) {
            console.error(error);
            toast.error("Upload failed", { description: "Could not reach the backend API." });
          }
        })();
        return "pending";
      }

      const id = nextId("INV");
      const mock = MOCK_SUPPLIERS[Math.floor(Math.random() * MOCK_SUPPLIERS.length)]!;
      const subtotal = 380 + Math.round(Math.random() * 900);
      const vat = Math.round(subtotal * 0.21 * 100) / 100;
      const invoice: Invoice = {
        id,
        file: fileName,
        clientId,
        supplier: mock.supplier,
        number: `NEW-2026-${Math.floor(1000 + Math.random() * 8999)}`,
        invoiceDate: "2026-08-12",
        dueDate: "2026-09-11",
        vatAmount: vat,
        total: Math.round((subtotal + vat) * 100) / 100,
        currency: "EUR",
        paymentRef: `0092 ${Math.floor(1000 + Math.random() * 8999)} 4417 2201`,
        uploadedAt: "2026-08-12",
        confidence: mock.conf,
        status: "New",
        lines: [
          { id: "l1", description: "Geleverde goederen en diensten", qty: 1, unitPrice: subtotal, vatRate: 21 },
        ],
        suggestion: {
          ledger: mock.ledger,
          vatCode: "NL 21%",
          category: "Operating expenses",
          relation: mock.supplier,
          reasoning: `Similar ${mock.supplier} invoices have previously been booked to ${mock.ledger} with VAT code NL21.`,
        },
      };
      setInvoices((prev) => [invoice, ...prev]);
      log("upload", `Document ${fileName} uploaded`, actor, clientId, id);
      toast.success("Document uploaded", { description: fileName });

      setTimeout(() => {
        patchInvoice(id, { status: "AI processed" });
      }, 700);
      setTimeout(() => {
        setInvoices((prev) =>
          prev.map((i) =>
            i.id === id
              ? { ...i, status: i.confidence >= settings.threshold ? "Auto-ready" : "Needs review" }
              : i,
          ),
        );
        log(
          "ai",
          `AI processed invoice ${invoice.number} with ${invoice.confidence}% confidence`,
          "Abdelouhab",
          clientId,
          id,
        );
        toast.success("AI processing complete", {
          description: `${invoice.supplier} · ${invoice.confidence}% confidence`,
        });
      }, 1800);

      return id;
    },
    [log, patchInvoice, refreshState, settings.threshold, usingApi],
  );

  const updateInvoiceFields = useCallback(
    (id: string, patch: Partial<Invoice>) => {
      if (usingApi) {
        void updateInvoiceApi(id, patch)
          .then(() => refreshState())
          .then(() => toast.success("Correction saved for future suggestions"))
          .catch(() => toast.error("Could not save invoice changes"));
        return;
      }

      patchInvoice(id, patch);
      const invoice = invoices.find((i) => i.id === id);
      log("edit", `Extracted fields corrected on ${invoice?.number ?? id}`, "Sara", invoice?.clientId, id);
      toast.success("Correction saved for future suggestions");
    },
    [invoices, log, patchInvoice, refreshState, usingApi],
  );

  const updateSuggestion = useCallback(
    (id: string, patch: Partial<Suggestion>) => {
      if (usingApi) {
        void updateSuggestionApi(id, patch)
          .then(() => refreshState())
          .then(() => toast.success("Correction saved for future suggestions"))
          .catch(() => toast.error("Could not save suggestion changes"));
        return;
      }

      setInvoices((prev) =>
        prev.map((i) =>
          i.id === id ? { ...i, suggestion: { ...i.suggestion, ...patch, editedBy: "Sara" } } : i,
        ),
      );
      const invoice = invoices.find((i) => i.id === id);
      log("edit", `Booking suggestion adjusted on ${invoice?.number ?? id}`, "Sara", invoice?.clientId, id);
      toast.success("Correction saved for future suggestions");
    },
    [invoices, log, refreshState, usingApi],
  );

  const setInvoiceStatus = useCallback(
    (id: string, status: InvoiceStatus) => {
      if (usingApi) {
        void setInvoiceStatusApi(id, status)
          .then(() => refreshState())
          .catch(() => toast.error("Could not update invoice status"));
        return;
      }
      patchInvoice(id, { status });
    },
    [patchInvoice, refreshState, usingApi],
  );

  const approveInvoice = useCallback(
    (id: string) => {
      if (usingApi) {
        void approveInvoiceApi(id)
          .then(() => refreshState())
          .then(() => toast.success("Invoice approved"))
          .catch(() => toast.error("Could not approve invoice"));
        return;
      }

      const invoice = invoices.find((i) => i.id === id);
      patchInvoice(id, { status: "Approved" });
      log("approve", `Invoice ${invoice?.number ?? id} approved`, "Sara", invoice?.clientId, id);
      toast.success("Invoice approved");
    },
    [invoices, log, patchInvoice, refreshState, usingApi],
  );

  const rejectInvoice = useCallback(
    (id: string) => {
      if (usingApi) {
        void rejectInvoiceApi(id)
          .then(() => refreshState())
          .then(() => toast("Invoice rejected"))
          .catch(() => toast.error("Could not reject invoice"));
        return;
      }

      const invoice = invoices.find((i) => i.id === id);
      patchInvoice(id, { status: "Rejected" });
      log("reject", `Invoice ${invoice?.number ?? id} rejected`, "Sara", invoice?.clientId, id);
      toast("Invoice rejected");
    },
    [invoices, log, patchInvoice, refreshState, usingApi],
  );

  const syncInvoice = useCallback(
    (id: string) => {
      const invoice = invoices.find((i) => i.id === id);
      if (!invoice) return;

      if (usingApi) {
        toast.loading("Syncing…", { id: `sync-${id}` });
        void syncInvoiceApi(id)
          .then(async () => {
            await refreshState();
            const latest = (await fetchLedgerState()).invoices.find((item) => item.id === id);
            if (latest?.status === "Synced") {
              toast.success("Synced to SnelStart", { id: `sync-${id}` });
            } else {
              toast.error("SnelStart connection unavailable", {
                id: `sync-${id}`,
                description: latest?.syncError ?? "SnelStart is not connected for this client.",
              });
            }
          })
          .catch(() => toast.error("Sync failed", { id: `sync-${id}` }));
        return;
      }

      const client = clients.find((c) => c.id === invoice.clientId);
      toast.loading("Syncing…", { id: `sync-${id}` });
      setTimeout(() => {
        if (client?.snelstart) {
          patchInvoice(id, { status: "Synced", syncError: undefined });
          log("sync", `Invoice ${invoice.number} synced to SnelStart`, "Abdelouhab", invoice.clientId, id);
          toast.success("Synced to SnelStart", { id: `sync-${id}` });
        } else {
          patchInvoice(id, {
            status: "Sync failed",
            syncError: "SnelStart is not connected for this client.",
          });
          log(
            "sync-failed",
            `Sync failed for ${invoice.number} — SnelStart not connected`,
            "Abdelouhab",
            invoice.clientId,
            id,
          );
          toast.error("SnelStart connection unavailable", {
            id: `sync-${id}`,
            description: "SnelStart is not connected for this client.",
          });
        }
      }, 1200);
    },
    [clients, invoices, log, patchInvoice, refreshState, usingApi],
  );

  const bulkApprove = useCallback(
    (ids: string[]) => {
      if (usingApi) {
        void bulkApproveApi(ids)
          .then(() => refreshState())
          .then(() => toast.success(`${ids.length} invoices approved`))
          .catch(() => toast.error("Bulk approve failed"));
        return;
      }

      setInvoices((prev) => prev.map((i) => (ids.includes(i.id) ? { ...i, status: "Approved" } : i)));
      log("approve", `${ids.length} invoices approved in bulk`, "Sara");
      toast.success(`${ids.length} invoices approved`);
    },
    [log, refreshState, usingApi],
  );

  const bulkNeedsReview = useCallback(
    (ids: string[]) => {
      if (usingApi) {
        void bulkNeedsReviewApi(ids)
          .then(() => refreshState())
          .then(() => toast(`${ids.length} invoices marked as needs review`))
          .catch(() => toast.error("Bulk update failed"));
        return;
      }

      setInvoices((prev) => prev.map((i) => (ids.includes(i.id) ? { ...i, status: "Needs review" } : i)));
      toast(`${ids.length} invoices marked as needs review`);
    },
    [refreshState, usingApi],
  );

  const createQuery = useCallback(
    (clientId: string, question: string, invoiceId?: string) => {
      if (usingApi) {
        void createQueryApi(clientId, question, invoiceId)
          .then(() => refreshState())
          .then(() => toast.success("Query sent to client"))
          .catch(() => toast.error("Could not send query"));
        return;
      }

      const q: ClientQuery = {
        id: nextId("Q"),
        clientId,
        invoiceId,
        question,
        createdAt: "2026-08-12",
        status: "Requested",
      };
      setQueries((prev) => [q, ...prev]);
      log("query", `Query sent: “${question.slice(0, 60)}”`, "Sara", clientId, invoiceId);
      toast.success("Query sent to client");
    },
    [log, refreshState, usingApi],
  );

  const answerQuery = useCallback(
    (id: string, answer: string, proof?: string) => {
      if (usingApi) {
        void answerQueryApi(id, answer, proof)
          .then(() => refreshState())
          .then(() => toast.success("Answer received"))
          .catch(() => toast.error("Could not send answer"));
        return;
      }

      setQueries((prev) =>
        prev.map((q) =>
          q.id === id ? { ...q, answer, proof, answeredAt: "2026-08-12", status: "Delivered" } : q,
        ),
      );
      const q = queries.find((x) => x.id === id);
      log("answer", `Client answered a query`, clientName(q?.clientId ?? ""), q?.clientId, q?.invoiceId);
      toast.success("Answer received");
    },
    [clientName, log, queries, refreshState, usingApi],
  );

  const approveQuery = useCallback(
    (id: string) => {
      if (usingApi) {
        void approveQueryApi(id)
          .then(() => refreshState())
          .then(() => toast.success("Query marked approved"))
          .catch(() => toast.error("Could not approve query"));
        return;
      }

      setQueries((prev) => prev.map((q) => (q.id === id ? { ...q, status: "Approved" } : q)));
      toast.success("Query marked approved");
    },
    [refreshState, usingApi],
  );

  const setTxStatus = useCallback(
    (id: string, status: BankTx["status"]) => {
      if (usingApi) {
        void setTxStatusApi(id, status)
          .then(() => refreshState())
          .then(() => toast.success(`Transaction ${status.toLowerCase()}`))
          .catch(() => toast.error("Could not update transaction"));
        return;
      }

      setTransactions((prev) => prev.map((t) => (t.id === id ? { ...t, status } : t)));
      const tx = transactions.find((t) => t.id === id);
      log("match", `Transaction ${tx?.description ?? id} marked ${status.toLowerCase()}`, "Sara", tx?.clientId);
      toast.success(`Transaction ${status.toLowerCase()}`);
    },
    [log, refreshState, transactions, usingApi],
  );

  const updateSettings = useCallback(
    (patch: Partial<Settings>) => {
      if (usingApi) {
        void updateSettingsApi(patch)
          .then(() => refreshState())
          .catch(() => toast.error("Could not update settings"));
        return;
      }
      setSettings((prev) => ({ ...prev, ...patch }));
    },
    [refreshState, usingApi],
  );

  const setClientRule = useCallback(
    (clientId: string, rule: string) => {
      if (usingApi) {
        void setClientRuleApi(clientId, rule)
          .then(() => refreshState())
          .then(() => toast.success("Rule saved"))
          .catch(() => toast.error("Could not save rule"));
        return;
      }

      setSettings((prev) => ({ ...prev, clientRules: { ...prev.clientRules, [clientId]: rule } }));
      toast.success("Rule saved");
    },
    [refreshState, usingApi],
  );

  const value = useMemo<Ctx>(
    () => ({
      ready,
      usingApi,
      clients,
      invoices,
      transactions,
      queries,
      activity,
      settings,
      clientName,
      getInvoice: (id: string) => invoices.find((i) => i.id === id),
      uploadDocument,
      updateInvoiceFields,
      updateSuggestion,
      setInvoiceStatus,
      approveInvoice,
      rejectInvoice,
      syncInvoice,
      bulkApprove,
      bulkNeedsReview,
      createQuery,
      answerQuery,
      approveQuery,
      setTxStatus,
      updateSettings,
      setClientRule,
    }),
    [
      activity,
      answerQuery,
      approveInvoice,
      approveQuery,
      bulkApprove,
      bulkNeedsReview,
      clientName,
      clients,
      createQuery,
      invoices,
      queries,
      ready,
      rejectInvoice,
      setClientRule,
      setInvoiceStatus,
      setTxStatus,
      settings,
      syncInvoice,
      transactions,
      updateInvoiceFields,
      updateSettings,
      updateSuggestion,
      uploadDocument,
      usingApi,
    ],
  );

  if (!ready) {
    return (
      <div className="flex min-h-screen items-center justify-center bg-background text-sm text-muted-foreground">
        Connecting to Abdelouhab API…
      </div>
    );
  }

  return <LedgerContext.Provider value={value}>{children}</LedgerContext.Provider>;
}

export function useLedger() {
  const ctx = useContext(LedgerContext);
  if (!ctx) throw new Error("useLedger must be used within LedgerProvider");
  return ctx;
}

export const eur = (n: number) =>
  new Intl.NumberFormat("en-IE", { style: "currency", currency: "EUR" }).format(n);

export function relativeTime(ts: number) {
  const diff = Math.round((Date.now() - ts) / 60000);
  if (diff < 1) return "just now";
  if (diff < 60) return `${diff} min ago`;
  const hours = Math.round(diff / 60);
  if (hours < 24) return `${hours} h ago`;
  return `${Math.round(hours / 24)} d ago`;
}
