"use client";

import { useCallback, useEffect, useMemo, useState } from "react";
import { createClient, getBrowserUserId } from "@/lib/supabase/client";
import { useWorkspaceStore } from "@/stores/workspace-store";
import type {
  Invoice,
  InvoiceCustomer,
  InvoiceDirection,
  InvoiceDocumentType,
  InvoiceEvent,
  InvoiceEventType,
  InvoiceFolder,
  InvoiceItem,
  InvoiceSource,
  InvoiceStatus,
} from "@/types";

export interface InvoiceCustomerUpsertInput {
  address?: string;
  contact_person?: string;
  email?: string;
  name: string;
  notes?: string;
  payment_terms_days?: number;
  vat_id?: string;
}

export interface InvoiceItemDraft {
  id?: string;
  description: string;
  quantity: number;
  sort_order?: number;
  unit_price_cents: number;
}

export interface InvoiceUpsertInput {
  accent_color?: string;
  company_address?: string;
  company_email?: string;
  company_name?: string;
  company_phone?: string;
  company_vat_id?: string;
  company_website?: string;
  counterparty_address?: string;
  counterparty_email?: string;
  counterparty_name: string;
  customer_id?: string | null;
  document_type?: InvoiceDocumentType;
  direction: InvoiceDirection;
  due_date: string;
  folder_id?: string | null;
  inbox_at?: string | null;
  inbox_note?: string;
  invoice_number: string;
  issue_date: string;
  items: InvoiceItemDraft[];
  notes?: string;
  source?: InvoiceSource;
  status: InvoiceStatus;
  tax_enabled?: boolean;
  tax_rate: number;
  title?: string;
  logo_url?: string;
}

export interface InvoiceFolderUpsertInput {
  color?: string;
  icon?: string;
  is_smart?: boolean;
  name: string;
  slug: string;
  sort_order?: number;
}

function toInvoicesErrorMessage(error: unknown) {
  const message = error instanceof Error ? error.message : "Invoice action failed.";

  if (/relation .*invoice_/i.test(message) || /invoices does not exist/i.test(message)) {
    return "Das Rechnungs-System ist noch nicht in der Datenbank aktiv. Führe zuerst die neue Migration aus.";
  }

  return message;
}

function normalizeInvoiceItems(items: InvoiceItemDraft[]) {
  return items
    .filter((item) => item.description.trim() || item.unit_price_cents > 0 || item.quantity > 0)
    .map((item, index) => ({
      description: item.description.trim(),
      quantity: Number.isFinite(item.quantity) ? item.quantity : 0,
      sort_order: item.sort_order ?? index,
      unit_price_cents: Number.isFinite(item.unit_price_cents) ? item.unit_price_cents : 0,
    }));
}

function computeInvoiceTotals(items: InvoiceItemDraft[], taxRate: number, taxEnabled = true) {
  const subtotal_cents = normalizeInvoiceItems(items).reduce(
    (sum, item) => sum + Math.round(item.quantity * item.unit_price_cents),
    0,
  );
  const effectiveTaxRate = taxEnabled ? taxRate : 0;
  const tax_cents = Math.round((subtotal_cents * effectiveTaxRate) / 100);
  const total_cents = subtotal_cents + tax_cents;

  return {
    subtotal_cents,
    tax_cents,
    total_cents,
  };
}

function sortInvoiceItems(items: InvoiceItem[]) {
  return [...items].sort(
    (left, right) =>
      left.sort_order - right.sort_order || left.created_at.localeCompare(right.created_at),
  );
}

function sortInvoiceEvents(events: InvoiceEvent[]) {
  return [...events].sort((left, right) => right.created_at.localeCompare(left.created_at));
}

export function useInvoices() {
  const [invoices, setInvoices] = useState<Invoice[]>([]);
  const [customers, setCustomers] = useState<InvoiceCustomer[]>([]);
  const [folders, setFolders] = useState<InvoiceFolder[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [userId, setUserId] = useState<string | null>(null);
  const supabase = createClient();
  const activeTeamId = useWorkspaceStore((state) => state.activeTeamId);
  const initialized = useWorkspaceStore((state) => state.initialized);

  useEffect(() => {
    void getBrowserUserId().then(setUserId).catch(() => setUserId(null));
  }, []);

  const requireCurrentUserId = useCallback(async () => {
    const currentUserId = userId || (await getBrowserUserId());
    if (currentUserId && currentUserId !== userId) {
      setUserId(currentUserId);
    }
    return currentUserId;
  }, [userId]);

  const logInvoiceEvent = useCallback(
    async (invoiceId: string, eventType: InvoiceEventType, title: string, details?: Record<string, unknown>) => {
      const currentUserId = await requireCurrentUserId();
      if (!currentUserId) throw new Error("Not authenticated");

      const { error: eventError } = await supabase.from("invoice_events").insert({
        details: details || {},
        event_type: eventType,
        invoice_id: invoiceId,
        team_id: activeTeamId,
        title,
        user_id: currentUserId,
      });

      if (eventError) throw eventError;
    },
    [activeTeamId, requireCurrentUserId, supabase],
  );

  const fetchInvoices = useCallback(async () => {
    if (!initialized) return;

    setLoading(true);
    setError(null);

    try {
      const currentUserId = await requireCurrentUserId();
      if (!currentUserId) {
        setInvoices([]);
        setCustomers([]);
        setFolders([]);
        setLoading(false);
        return;
      }

      try {
        await supabase.rpc("ensure_invoice_folders", {
          target_team_uuid: activeTeamId,
        });
      } catch (rpcError) {
        console.warn("Could not seed invoice folders", rpcError);
      }

      let customersQuery = supabase
        .from("invoice_customers")
        .select("*")
        .order("name", { ascending: true });

      let foldersQuery = supabase
        .from("invoice_folders")
        .select("*")
        .order("sort_order")
        .order("created_at", { ascending: true });

      let invoicesQuery = supabase
        .from("invoices")
        .select("*")
        .order("created_at", { ascending: false });

      customersQuery = activeTeamId
        ? customersQuery.eq("team_id", activeTeamId)
        : customersQuery.is("team_id", null);

      foldersQuery = activeTeamId
        ? foldersQuery.eq("team_id", activeTeamId)
        : foldersQuery.is("team_id", null);

      invoicesQuery = activeTeamId
        ? invoicesQuery.eq("team_id", activeTeamId)
        : invoicesQuery.is("team_id", null);

      const [
        { data: customersData, error: customersError },
        { data: foldersData, error: foldersError },
        { data: invoicesData, error: invoicesError },
      ] = await Promise.all([customersQuery, foldersQuery, invoicesQuery]);

      if (customersError) throw customersError;
      if (foldersError) throw foldersError;
      if (invoicesError) throw invoicesError;

      const customerRows = (customersData || []) as InvoiceCustomer[];
      const folderRows = (foldersData || []) as InvoiceFolder[];
      const invoiceRows = (invoicesData || []) as Invoice[];
      const invoiceIds = invoiceRows.map((invoice) => invoice.id);

      const [itemsRes, eventsRes] = await Promise.all([
        invoiceIds.length > 0
          ? supabase.from("invoice_items").select("*").in("invoice_id", invoiceIds)
          : Promise.resolve({ data: [] as InvoiceItem[], error: null }),
        invoiceIds.length > 0
          ? supabase.from("invoice_events").select("*").in("invoice_id", invoiceIds)
          : Promise.resolve({ data: [] as InvoiceEvent[], error: null }),
      ]);

      if (itemsRes.error) throw itemsRes.error;
      if (eventsRes.error) throw eventsRes.error;

      const customerMap = new Map(customerRows.map((customer) => [customer.id, customer]));
      const folderMap = new Map(folderRows.map((folder) => [folder.id, folder]));
      const itemsByInvoiceId = new Map<string, InvoiceItem[]>();
      const eventsByInvoiceId = new Map<string, InvoiceEvent[]>();

      ((itemsRes.data || []) as InvoiceItem[]).forEach((item) => {
        const bucket = itemsByInvoiceId.get(item.invoice_id) || [];
        bucket.push(item);
        itemsByInvoiceId.set(item.invoice_id, bucket);
      });

      ((eventsRes.data || []) as InvoiceEvent[]).forEach((event) => {
        const bucket = eventsByInvoiceId.get(event.invoice_id) || [];
        bucket.push(event);
        eventsByInvoiceId.set(event.invoice_id, bucket);
      });

      setCustomers(customerRows);
      setFolders(folderRows);
      setInvoices(
        invoiceRows.map((invoice) => ({
          ...invoice,
          customer: invoice.customer_id ? customerMap.get(invoice.customer_id) || null : null,
          folder: invoice.folder_id ? folderMap.get(invoice.folder_id) || null : null,
          events: sortInvoiceEvents(eventsByInvoiceId.get(invoice.id) || []),
          items: sortInvoiceItems(itemsByInvoiceId.get(invoice.id) || []),
        })),
      );
    } catch (fetchError) {
      setError(toInvoicesErrorMessage(fetchError));
      setCustomers([]);
      setFolders([]);
      setInvoices([]);
    } finally {
      setLoading(false);
    }
  }, [activeTeamId, initialized, requireCurrentUserId, supabase]);

  useEffect(() => {
    if (!initialized) return;
    const timer = window.setTimeout(() => {
      void fetchInvoices();
    }, 0);

    return () => window.clearTimeout(timer);
  }, [fetchInvoices, initialized]);

  const createCustomer = useCallback(
    async (input: InvoiceCustomerUpsertInput) => {
      const currentUserId = await requireCurrentUserId();
      if (!currentUserId) throw new Error("Not authenticated");

      const { data, error: createError } = await supabase
        .from("invoice_customers")
        .insert({
          address: input.address?.trim() || "",
          contact_person: input.contact_person?.trim() || "",
          email: input.email?.trim() || "",
          name: input.name.trim(),
          notes: input.notes?.trim() || "",
          payment_terms_days: input.payment_terms_days ?? 30,
          team_id: activeTeamId,
          user_id: currentUserId,
          vat_id: input.vat_id?.trim() || "",
        })
        .select("*")
        .single();

      if (createError) throw createError;

      await fetchInvoices();
      return data as InvoiceCustomer;
    },
    [activeTeamId, fetchInvoices, requireCurrentUserId, supabase],
  );

  const updateCustomer = useCallback(
    async (customerId: string, input: InvoiceCustomerUpsertInput) => {
      const { error: updateError } = await supabase
        .from("invoice_customers")
        .update({
          address: input.address?.trim() || "",
          contact_person: input.contact_person?.trim() || "",
          email: input.email?.trim() || "",
          name: input.name.trim(),
          notes: input.notes?.trim() || "",
          payment_terms_days: input.payment_terms_days ?? 30,
          vat_id: input.vat_id?.trim() || "",
        })
        .eq("id", customerId);

      if (updateError) throw updateError;

      await fetchInvoices();
    },
    [fetchInvoices, supabase],
  );

  const deleteCustomer = useCallback(
    async (customerId: string) => {
      const { error: deleteError } = await supabase.from("invoice_customers").delete().eq("id", customerId);
      if (deleteError) throw deleteError;
      await fetchInvoices();
    },
    [fetchInvoices, supabase],
  );

  const saveInvoice = useCallback(
    async (input: InvoiceUpsertInput, existingId?: string | null) => {
      const currentUserId = await requireCurrentUserId();
      if (!currentUserId) throw new Error("Not authenticated");

      const normalizedItems = normalizeInvoiceItems(input.items);
      const taxEnabled = input.tax_enabled ?? input.tax_rate > 0;
      const totals = computeInvoiceTotals(normalizedItems, input.tax_rate, taxEnabled);

      const payload = {
        accent_color: input.accent_color?.trim() || "#7dd3fc",
        company_address: input.company_address?.trim() || "",
        company_email: input.company_email?.trim() || "",
        company_name: input.company_name?.trim() || "Mindavio",
        company_phone: input.company_phone?.trim() || "",
        company_vat_id: input.company_vat_id?.trim() || "",
        company_website: input.company_website?.trim() || "",
        counterparty_address: input.counterparty_address?.trim() || "",
        counterparty_email: input.counterparty_email?.trim() || "",
        counterparty_name: input.counterparty_name.trim(),
        currency: "EUR",
        customer_id: input.customer_id || null,
        document_type: input.document_type || "invoice",
        direction: input.direction,
        due_date: input.due_date,
        folder_id: input.folder_id || null,
        inbox_at: input.inbox_at || null,
        inbox_note: input.inbox_note?.trim() || "",
        invoice_number: input.invoice_number.trim(),
        issue_date: input.issue_date,
        logo_url: input.logo_url?.trim() || "",
        notes: input.notes?.trim() || "",
        source: input.source || "manual",
        status: input.status,
        tax_enabled: taxEnabled,
        tax_rate: taxEnabled ? input.tax_rate : 0,
        team_id: activeTeamId,
        title: input.title?.trim() || "",
        user_id: currentUserId,
        ...totals,
      };

      let invoiceId = existingId || null;
      let previousItemIds: string[] = [];

      if (invoiceId) {
        const { error: updateError } = await supabase.from("invoices").update(payload).eq("id", invoiceId);
        if (updateError) throw updateError;

        // Fetch the old item ids now, but don't delete them yet — if the
        // insert below fails, the invoice must keep its existing items
        // rather than being left with none while its totals still reflect
        // the old items.
        const { data: existingItemRows, error: existingItemsError } = await supabase
          .from("invoice_items")
          .select("id")
          .eq("invoice_id", invoiceId);
        if (existingItemsError) throw existingItemsError;
        previousItemIds = (existingItemRows || []).map((row) => row.id as string);
      } else {
        const { data, error: insertError } = await supabase
          .from("invoices")
          .insert(payload)
          .select("id")
          .single();

        if (insertError) throw insertError;
        invoiceId = data.id as string;
      }

      if (normalizedItems.length > 0) {
        const { error: itemsError } = await supabase.from("invoice_items").insert(
          normalizedItems.map((item, index) => ({
            description: item.description,
            invoice_id: invoiceId,
            quantity: item.quantity,
            sort_order: index,
            team_id: activeTeamId,
            unit_price_cents: item.unit_price_cents,
            user_id: currentUserId,
          })),
        );

        if (itemsError) throw itemsError;
      }

      if (previousItemIds.length > 0) {
        const { error: clearItemsError } = await supabase.from("invoice_items").delete().in("id", previousItemIds);
        if (clearItemsError) throw clearItemsError;
      }

      await logInvoiceEvent(
        invoiceId,
        existingId ? "updated" : "created",
        existingId
          ? input.document_type === "quote"
            ? "Angebot aktualisiert"
            : "Rechnung aktualisiert"
          : input.document_type === "quote"
            ? "Angebot erstellt"
            : "Rechnung erstellt",
        {
          document_type: input.document_type || "invoice",
          direction: input.direction,
          folder_id: input.folder_id || null,
          source: input.source || "manual",
          status: input.status,
          total_cents: totals.total_cents,
        },
      );

      await fetchInvoices();
      return invoiceId;
    },
    [activeTeamId, fetchInvoices, logInvoiceEvent, requireCurrentUserId, supabase],
  );

  const deleteInvoice = useCallback(
    async (invoiceId: string) => {
      const { error: deleteError } = await supabase.from("invoices").delete().eq("id", invoiceId);
      if (deleteError) throw deleteError;
      await fetchInvoices();
    },
    [fetchInvoices, supabase],
  );

  const updateInvoiceStatus = useCallback(
    async (invoiceId: string, status: InvoiceStatus) => {
      // Only stamp the timestamp for the status being set now — leaving the
      // other two columns out of the payload keeps their history intact
      // (e.g. marking an invoice "paid" must not erase when it was "sent").
      const payload: Partial<Invoice> & Record<string, unknown> = { status };
      if (status === "sent") payload.sent_at = new Date().toISOString();
      if (status === "paid") payload.paid_at = new Date().toISOString();
      if (status === "cancelled") payload.cancelled_at = new Date().toISOString();

      const { error: updateError } = await supabase.from("invoices").update(payload).eq("id", invoiceId);
      if (updateError) throw updateError;

      await logInvoiceEvent(invoiceId, status === "paid" ? "marked_paid" : "status_changed", `Status geändert: ${status}`, {
        status,
      });

      await fetchInvoices();
    },
    [fetchInvoices, logInvoiceEvent, supabase],
  );

  const moveInvoiceToInbox = useCallback(
    async (invoiceId: string, note?: string) => {
      const inboxAt = new Date().toISOString();
      const { error: updateError } = await supabase
        .from("invoices")
        .update({
          inbox_at: inboxAt,
          inbox_note: note?.trim() || "",
        })
        .eq("id", invoiceId);

      if (updateError) throw updateError;

      await logInvoiceEvent(invoiceId, "updated", "In Inbox verschoben", {
        inbox_at: inboxAt,
      });
      await fetchInvoices();
    },
    [fetchInvoices, logInvoiceEvent, supabase],
  );

  const removeInvoiceFromInbox = useCallback(
    async (invoiceId: string) => {
      const { error: updateError } = await supabase
        .from("invoices")
        .update({
          inbox_at: null,
          inbox_note: "",
        })
        .eq("id", invoiceId);

      if (updateError) throw updateError;

      await logInvoiceEvent(invoiceId, "updated", "Aus Inbox entfernt");
      await fetchInvoices();
    },
    [fetchInvoices, logInvoiceEvent, supabase],
  );

  const preparedOutgoingCount = useMemo(
    () => invoices.filter((invoice) => invoice.direction === "outgoing").length,
    [invoices],
  );

  const createFolder = useCallback(
    async (input: InvoiceFolderUpsertInput) => {
      const currentUserId = await requireCurrentUserId();
      if (!currentUserId) throw new Error("Not authenticated");

      const { data, error: createError } = await supabase
        .from("invoice_folders")
        .insert({
          color: input.color || "#8b5cf6",
          icon: input.icon || "folder",
          is_smart: input.is_smart ?? false,
          name: input.name.trim(),
          slug: input.slug.trim(),
          sort_order: input.sort_order ?? folders.length,
          team_id: activeTeamId,
          user_id: currentUserId,
        })
        .select("*")
        .single();

      if (createError) throw createError;
      await fetchInvoices();
      return data as InvoiceFolder;
    },
    [activeTeamId, fetchInvoices, folders.length, requireCurrentUserId, supabase],
  );

  return {
    createCustomer,
    createFolder,
    customers,
    deleteCustomer,
    deleteInvoice,
    error,
    folders,
    invoices,
    loading,
    preparedOutgoingCount,
    refetch: fetchInvoices,
    moveInvoiceToInbox,
    removeInvoiceFromInbox,
    saveInvoice,
    updateCustomer,
    updateInvoiceStatus,
    logInvoiceEvent,
  };
}
