"use client";

import { useState, useMemo, useEffect, useCallback } from "react";
import { useSearchParams } from "next/navigation";
import { PageLayout, PageHeader } from "@/components/layout";
import { RefreshCw } from "lucide-react";
import {
  TransactionFilters,
  TransactionBulkActions,
  TransactionTable,
} from "@/components/transactions";
import { RuleCreateDialog } from "@/components/rules";
import { OFXImportDialog } from "@/components/import/ofx-import-dialog";
import {
  useBankingMetadata,
  useTransactions,
  getTransactionsQueryKey,
  useDuplicateIds,
} from "@/lib/hooks";
import { updateCategory } from "@/lib/store-db";
import { useQueryClient } from "@tanstack/react-query";
import { Button } from "@/components/ui/button";
import { Upload } from "lucide-react";
import type { Transaction } from "@/lib/types";
import type { TransactionsPaginatedParams } from "@/services/banking.service";
import {
  normalizeDescription,
  suggestKeyword,
} from "@/components/rules/constants";

type SortField = "date" | "amount" | "description";
type SortOrder = "asc" | "desc";
type Period = "1month" | "3months" | "6months" | "12months" | "custom" | "all";

const PAGE_SIZE = 100;

export default function TransactionsPage() {
  const searchParams = useSearchParams();
  const queryClient = useQueryClient();
  const { data: metadata, isLoading: isLoadingMetadata } = useBankingMetadata();
  const [searchQuery, setSearchQuery] = useState("");
  const [debouncedSearchQuery, setDebouncedSearchQuery] = useState("");
  const [selectedAccounts, setSelectedAccounts] = useState<string[]>(["all"]);
  const [page, setPage] = useState(0);

  // Debounce search query
  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedSearchQuery(searchQuery);
      setPage(0); // Reset to first page when search changes
    }, 300);
    return () => clearTimeout(timer);
  }, [searchQuery]);

  useEffect(() => {
    const accountId = searchParams.get("accountId");
    if (accountId) {
      setSelectedAccounts([accountId]);
      setPage(0);
    }
  }, [searchParams]);

  const [selectedCategories, setSelectedCategories] = useState<string[]>([
    "all",
  ]);
  const [showReconciled, setShowReconciled] = useState<string>("all");
  const [period, setPeriod] = useState<Period>("all");
  const [customStartDate, setCustomStartDate] = useState<Date | undefined>(
    undefined
  );
  const [customEndDate, setCustomEndDate] = useState<Date | undefined>(
    undefined
  );
  const [isCustomDatePickerOpen, setIsCustomDatePickerOpen] = useState(false);
  const [sortField, setSortField] = useState<SortField>("date");
  const [sortOrder, setSortOrder] = useState<SortOrder>("desc");
  const [selectedTransactions, setSelectedTransactions] = useState<Set<string>>(
    new Set()
  );
  const [ruleDialogOpen, setRuleDialogOpen] = useState(false);
  const [ruleTransaction, setRuleTransaction] = useState<Transaction | null>(
    null
  );
  const [updatingTransactionIds, setUpdatingTransactionIds] = useState<
    Set<string>
  >(new Set());
  const [showDuplicates, setShowDuplicates] = useState(false);

  // Get start date based on period
  const startDate = useMemo(() => {
    const now = new Date();
    switch (period) {
      case "1month":
        return new Date(now.getFullYear(), now.getMonth() - 1, 1);
      case "3months":
        return new Date(now.getFullYear(), now.getMonth() - 3, 1);
      case "6months":
        return new Date(now.getFullYear(), now.getMonth() - 6, 1);
      case "12months":
        return new Date(now.getFullYear(), now.getMonth() - 12, 1);
      case "custom":
        return customStartDate || new Date(0);
      default:
        return new Date(0);
    }
  }, [period, customStartDate]);

  // Get end date (only for custom period)
  const endDate = useMemo(() => {
    if (period === "custom" && customEndDate) {
      return customEndDate;
    }
    return undefined;
  }, [period, customEndDate]);

  // Build transaction query params
  const transactionParams = useMemo(() => {
    const params: TransactionsPaginatedParams = {
      limit: PAGE_SIZE,
      offset: page * PAGE_SIZE,
      sortField,
      sortOrder,
    };

    if (startDate && period !== "all") {
      params.startDate = startDate.toISOString().split("T")[0];
    }
    if (endDate) {
      params.endDate = endDate.toISOString().split("T")[0];
    }
    if (!selectedAccounts.includes("all")) {
      params.accountIds = selectedAccounts;
    }
    if (!selectedCategories.includes("all")) {
      if (selectedCategories.includes("uncategorized")) {
        params.includeUncategorized = true;
      } else {
        params.categoryIds = selectedCategories;
      }
    }
    if (debouncedSearchQuery) {
      params.search = debouncedSearchQuery;
    }
    if (showReconciled !== "all") {
      params.isReconciled = showReconciled === "reconciled";
    }

    return params;
  }, [
    page,
    startDate,
    endDate,
    selectedAccounts,
    selectedCategories,
    debouncedSearchQuery,
    showReconciled,
    sortField,
    sortOrder,
    period,
  ]);

  // Fetch transactions with pagination
  const {
    data: transactionsData,
    isLoading: isLoadingTransactions,
    invalidate: invalidateTransactions,
  } = useTransactions(transactionParams, !!metadata);

  // Fetch duplicate IDs
  const { data: duplicateIds = new Set<string>() } = useDuplicateIds();

  // For filter comboboxes, we'll use empty arrays for now
  // They can be enhanced later with separate queries if needed
  const transactionsForAccountFilter: Transaction[] = [];
  const transactionsForCategoryFilter: Transaction[] = [];

  const handleCreateRule = useCallback((transaction: Transaction) => {
    setRuleTransaction(transaction);
    setRuleDialogOpen(true);
  }, []);

  // Create a virtual group for the rule dialog based on selected transaction
  // Note: This requires fetching similar transactions - simplified for now
  const ruleGroup = useMemo(() => {
    if (!ruleTransaction || !transactionsData) return null;

    // Use transactions from current page to find similar ones
    const normalizedDesc = normalizeDescription(ruleTransaction.description);
    const similarTransactions = transactionsData.transactions.filter(
      (t) => normalizeDescription(t.description) === normalizedDesc
    );

    if (similarTransactions.length === 0) return null;

    return {
      key: normalizedDesc,
      displayName: ruleTransaction.description,
      transactions: similarTransactions,
      totalAmount: similarTransactions.reduce((sum, t) => sum + t.amount, 0),
      suggestedKeyword: suggestKeyword(
        similarTransactions.map((t) => t.description)
      ),
    };
  }, [ruleTransaction, transactionsData]);

  const handleSaveRule = useCallback(
    async (ruleData: {
      keyword: string;
      categoryId: string;
      applyToExisting: boolean;
      transactionIds: string[];
    }) => {
      if (!metadata) return;

      // 1. Add keyword to category
      const category = metadata.categories.find(
        (c: { id: string }) => c.id === ruleData.categoryId
      );
      if (!category) {
        throw new Error("Category not found");
      }

      // Check if keyword already exists
      const keywordExists = category.keywords.some(
        (k: string) => k.toLowerCase() === ruleData.keyword.toLowerCase()
      );

      if (!keywordExists) {
        await updateCategory({
          ...category,
          keywords: [...category.keywords, ruleData.keyword],
        });
      }

      // 2. Apply to existing transactions if requested
      if (ruleData.applyToExisting) {
        await Promise.all(
          ruleData.transactionIds.map((id) =>
            fetch("/api/banking/transactions", {
              method: "PUT",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify({ id, categoryId: ruleData.categoryId }),
            })
          )
        );
      }

      // Invalidate queries
      queryClient.invalidateQueries({ queryKey: ["transactions"] });
      queryClient.invalidateQueries({ queryKey: ["banking-metadata"] });
      setRuleDialogOpen(false);
    },
    [metadata, queryClient]
  );

  const invalidateAll = useCallback(() => {
    invalidateTransactions();
    queryClient.invalidateQueries({ queryKey: ["banking-metadata"] });
  }, [invalidateTransactions, queryClient]);

  const formatCurrency = (amount: number) => {
    return new Intl.NumberFormat("fr-FR", {
      style: "currency",
      currency: "EUR",
    }).format(amount);
  };

  const formatDate = (dateStr: string) => {
    return new Date(dateStr).toLocaleDateString("fr-FR", {
      day: "2-digit",
      month: "short",
      year: "numeric",
    });
  };

  const toggleReconciled = async (transactionId: string) => {
    if (!transactionsData) return;

    const transaction = transactionsData.transactions.find(
      (t) => t.id === transactionId
    );
    if (!transaction) return;

    const updatedTransaction = {
      ...transaction,
      isReconciled: !transaction.isReconciled,
    };

    try {
      await fetch("/api/banking/transactions", {
        method: "PUT",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(updatedTransaction),
      });
      invalidateTransactions();
    } catch (error) {
      console.error("Failed to update transaction:", error);
    }
  };

  const markReconciled = async (transactionId: string) => {
    if (!transactionsData) return;

    const transaction = transactionsData.transactions.find(
      (t) => t.id === transactionId
    );
    if (!transaction || transaction.isReconciled) return;

    const updatedTransaction = {
      ...transaction,
      isReconciled: true,
    };

    try {
      await fetch("/api/banking/transactions", {
        method: "PUT",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(updatedTransaction),
      });
      invalidateTransactions();
    } catch (error) {
      console.error("Failed to update transaction:", error);
    }
  };

  const setCategory = async (
    transactionId: string,
    categoryId: string | null
  ) => {
    if (!transactionsData) return;

    const transaction = transactionsData.transactions.find(
      (t) => t.id === transactionId
    );
    if (!transaction) return;

    setUpdatingTransactionIds((prev) => new Set(prev).add(transactionId));

    try {
      const response = await fetch("/api/banking/transactions", {
        method: "PUT",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ ...transaction, categoryId }),
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      // Mise à jour directe du cache après succès
      const queryKey = getTransactionsQueryKey(transactionParams);
      queryClient.setQueryData<typeof transactionsData>(queryKey, (oldData) => {
        if (!oldData) return oldData;

        return {
          ...oldData,
          transactions: oldData.transactions.map((t) =>
            t.id === transactionId ? { ...t, categoryId } : t
          ),
        };
      });
    } catch (error) {
      console.error("Failed to update transaction:", error);
      invalidateTransactions();
    } finally {
      setUpdatingTransactionIds((prev) => {
        const next = new Set(prev);
        next.delete(transactionId);
        return next;
      });
    }
  };

  const bulkReconcile = async (reconciled: boolean) => {
    if (!transactionsData) return;

    const transactionsToUpdate = transactionsData.transactions.filter((t) =>
      selectedTransactions.has(t.id)
    );

    setSelectedTransactions(new Set());

    try {
      await Promise.all(
        transactionsToUpdate.map((t) =>
          fetch("/api/banking/transactions", {
            method: "PUT",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ ...t, isReconciled: reconciled }),
          })
        )
      );
      invalidateTransactions();
    } catch (error) {
      console.error("Failed to update transactions:", error);
    }
  };

  const bulkSetCategory = async (categoryId: string | null) => {
    if (!transactionsData) return;

    const transactionsToUpdate = transactionsData.transactions.filter((t) =>
      selectedTransactions.has(t.id)
    );

    const transactionIds = transactionsToUpdate.map((t) => t.id);
    setSelectedTransactions(new Set());
    setUpdatingTransactionIds((prev) => {
      const next = new Set(prev);
      transactionIds.forEach((id) => next.add(id));
      return next;
    });

    try {
      await Promise.all(
        transactionsToUpdate.map((t) =>
          fetch("/api/banking/transactions", {
            method: "PUT",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ ...t, categoryId }),
          })
        )
      );

      // Mise à jour directe du cache après succès
      const queryKey = getTransactionsQueryKey(transactionParams);
      queryClient.setQueryData<typeof transactionsData>(queryKey, (oldData) => {
        if (!oldData) return oldData;
        return {
          ...oldData,
          transactions: oldData.transactions.map((t) =>
            transactionIds.includes(t.id) ? { ...t, categoryId } : t
          ),
        };
      });
    } catch (error) {
      console.error("Failed to update transactions:", error);
      invalidateTransactions();
    } finally {
      setUpdatingTransactionIds((prev) => {
        const next = new Set(prev);
        transactionIds.forEach((id) => next.delete(id));
        return next;
      });
    }
  };

  const toggleSelectAll = () => {
    if (!transactionsData) return;
    if (selectedTransactions.size === transactionsData.transactions.length) {
      setSelectedTransactions(new Set());
    } else {
      setSelectedTransactions(
        new Set(transactionsData.transactions.map((t) => t.id))
      );
    }
  };

  const toggleSelectTransaction = (id: string) => {
    const newSelected = new Set(selectedTransactions);
    if (newSelected.has(id)) {
      newSelected.delete(id);
    } else {
      newSelected.add(id);
    }
    setSelectedTransactions(newSelected);
  };

  const handleSortChange = (field: SortField) => {
    if (sortField === field) {
      setSortOrder(sortOrder === "asc" ? "desc" : "asc");
    } else {
      setSortField(field);
      setSortOrder(field === "date" ? "desc" : "asc");
    }
    setPage(0);
  };

  const deleteTransaction = async (transactionId: string) => {
    // Remove from selected if selected
    const newSelected = new Set(selectedTransactions);
    newSelected.delete(transactionId);
    setSelectedTransactions(newSelected);

    // Sauvegarder les données actuelles pour pouvoir les restaurer en cas d'erreur
    const queryKey = getTransactionsQueryKey(transactionParams);
    const previousData =
      queryClient.getQueryData<typeof transactionsData>(queryKey);

    // Mise à jour optimiste du cache
    queryClient.setQueryData<typeof transactionsData>(queryKey, (oldData) => {
      if (!oldData) return oldData;
      return {
        ...oldData,
        transactions: oldData.transactions.filter(
          (t) => t.id !== transactionId
        ),
        total: oldData.total - 1,
      };
    });

    try {
      const response = await fetch(
        `/api/banking/transactions?id=${transactionId}`,
        {
          method: "DELETE",
        }
      );

      if (!response.ok) {
        const errorData = await response.json().catch(() => ({}));
        throw new Error(
          errorData.error || `Failed to delete transaction: ${response.status}`
        );
      }

      // Ne pas invalider immédiatement - la mise à jour optimiste est déjà correcte
      // On invalide seulement les autres queries qui pourraient être affectées (métadonnées, stats)
      queryClient.invalidateQueries({ queryKey: ["banking-metadata"] });
    } catch (error) {
      console.error("Failed to delete transaction:", error);
      // Restaurer les données précédentes en cas d'erreur
      if (previousData) {
        queryClient.setQueryData(queryKey, previousData);
      }
      // Invalider pour récupérer les données correctes du serveur
      invalidateTransactions();
    }
  };

  const filteredTransactions = transactionsData?.transactions || [];
  const totalTransactions = transactionsData?.total || 0;
  const hasMore = transactionsData?.hasMore || false;

  // Early return for loading state - prevents sidebar flash
  if (isLoadingMetadata || !metadata) {
    return (
      <div className="flex h-screen bg-background">
        <main className="flex-1 flex items-center justify-center">
          <RefreshCw className="w-8 h-8 animate-spin text-muted-foreground" />
        </main>
      </div>
    );
  }

  return (
    <PageLayout>
      <PageHeader
        title="Transactions"
        description={`${totalTransactions} transaction${totalTransactions > 1 ? "s" : ""}`}
        actions={
          <OFXImportDialog onImportComplete={invalidateAll}>
            <Button className="md:h-10 md:px-5">
              <Upload className="w-4 h-4 md:mr-2" />
              <span className="hidden md:inline">Importer OFX</span>
            </Button>
          </OFXImportDialog>
        }
      />

      <TransactionFilters
        searchQuery={searchQuery}
        onSearchChange={setSearchQuery}
        selectedAccounts={selectedAccounts}
        onAccountsChange={(accounts) => {
          setSelectedAccounts(accounts);
          setPage(0);
        }}
        selectedCategories={selectedCategories}
        onCategoriesChange={(categories) => {
          setPage(0);
          setSelectedCategories(categories);
        }}
        showReconciled={showReconciled}
        onReconciledChange={(value) => {
          setShowReconciled(value);
          setPage(0);
        }}
        period={period}
        onPeriodChange={(p) => {
          setPeriod(p);
          setPage(0);
          if (p !== "custom") {
            setIsCustomDatePickerOpen(false);
          } else {
            setIsCustomDatePickerOpen(true);
          }
        }}
        customStartDate={customStartDate}
        customEndDate={customEndDate}
        onCustomStartDateChange={(date) => {
          setCustomStartDate(date);
          setPage(0);
        }}
        onCustomEndDateChange={(date) => {
          setCustomEndDate(date);
          setPage(0);
        }}
        isCustomDatePickerOpen={isCustomDatePickerOpen}
        onCustomDatePickerOpenChange={setIsCustomDatePickerOpen}
        showDuplicates={showDuplicates}
        onShowDuplicatesChange={setShowDuplicates}
        accounts={metadata.accounts}
        folders={metadata.folders}
        categories={metadata.categories}
        transactionsForAccountFilter={transactionsForAccountFilter}
        transactionsForCategoryFilter={transactionsForCategoryFilter}
      />

      <TransactionBulkActions
        selectedCount={selectedTransactions.size}
        categories={metadata.categories}
        onReconcile={bulkReconcile}
        onSetCategory={bulkSetCategory}
      />

      {isLoadingTransactions ? (
        <div className="flex items-center justify-center py-12">
          <RefreshCw className="w-8 h-8 animate-spin text-muted-foreground" />
        </div>
      ) : (
        <>
          <TransactionTable
            transactions={filteredTransactions}
            accounts={metadata.accounts}
            categories={metadata.categories}
            selectedTransactions={selectedTransactions}
            sortField={sortField}
            sortOrder={sortOrder}
            onSortChange={handleSortChange}
            onToggleSelectAll={toggleSelectAll}
            onToggleSelectTransaction={toggleSelectTransaction}
            onToggleReconciled={toggleReconciled}
            onMarkReconciled={markReconciled}
            onSetCategory={setCategory}
            onCreateRule={handleCreateRule}
            onDelete={deleteTransaction}
            formatCurrency={formatCurrency}
            formatDate={formatDate}
            updatingTransactionIds={updatingTransactionIds}
            duplicateIds={duplicateIds}
            highlightDuplicates={showDuplicates}
          />

          {/* Pagination controls */}
          {totalTransactions > PAGE_SIZE && (
            <div className="flex items-center justify-between mt-4">
              <div className="text-sm text-muted-foreground">
                Affichage de {page * PAGE_SIZE + 1} à{" "}
                {Math.min((page + 1) * PAGE_SIZE, totalTransactions)} sur{" "}
                {totalTransactions}
              </div>
              <div className="flex gap-2">
                <Button
                  variant="outline"
                  size="sm"
                  onClick={() => setPage((p) => Math.max(0, p - 1))}
                  disabled={page === 0}
                >
                  Précédent
                </Button>
                <Button
                  variant="outline"
                  size="sm"
                  onClick={() => setPage((p) => p + 1)}
                  disabled={!hasMore}
                >
                  Suivant
                </Button>
              </div>
            </div>
          )}
        </>
      )}

      <RuleCreateDialog
        open={ruleDialogOpen}
        onOpenChange={setRuleDialogOpen}
        group={ruleGroup}
        categories={metadata.categories}
        onSave={handleSaveRule}
      />
    </PageLayout>
  );
}
