import { useState, useEffect, useMemo, useRef } from "react";
import { supabase } from "@/integrations/supabase/client";
import { useAuth } from "@/contexts/AuthContext";
import { useToast } from "@/hooks/use-toast";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Checkbox } from "@/components/ui/checkbox";
import { Badge } from "@/components/ui/badge";
import { CalendarIcon, DollarSign, Search, Loader2, Send, FileText, Ban, ShieldOff, Zap } from "lucide-react";
import { toast as sonnerToast } from "sonner";
import { format } from "date-fns";
import { cn } from "@/lib/utils";
import jsPDF from "jspdf";
import autoTable from "jspdf-autotable";
import { calculateRemainingUnpaidAssessmentBalance } from "@/lib/unitLedgerAccountBreakdown";

interface FeeExclusion {
  fee_type: "late_fee" | "interest";
  mode: "waive" | "override_amount" | "override_percent";
  override_amount: number | null;
  override_percent: number | null;
}

interface ChartAccount {
  id: string;
  account_number: string;
  account_name: string;
}

interface AccountRow {
  unitKey: string;
  ownerIds: string[];
  ownerNames: string[];
  unitId: string | null;
  unitNumber: string;
  address: string;
  associationId: string;
  associationName: string;
  balance: number;
  assessmentBalance: number;
  lastPaymentDate: string | null;
  lastInterestDate: string | null;
  lastLateFeeDate: string | null;
  lateFeeInput: string;
  interestInput: string;
  otherInput: string;
  otherDescription: string;
  otherAccountId: string;
  billToAssociation: boolean;
  lateFeeExclusion?: FeeExclusion | null;
  interestExclusion?: FeeExclusion | null;
}

export default function BulkChargesPage() {
  const { user } = useAuth();
  const { toast } = useToast();
  const [loading, setLoading] = useState(true);
  const [submitting, setSubmitting] = useState(false);
  const [rows, setRows] = useState<AccountRow[]>([]);
  const [associations, setAssociations] = useState<{ id: string; name: string }[]>([]);
  const [selectedAssociation, setSelectedAssociation] = useState("all");
  const [search, setSearch] = useState("");
  const [selected, setSelected] = useState<Set<string>>(new Set());
  const [lastApplied, setLastApplied] = useState<{ ownerNames: string[]; unitNumber: string; address: string; associationName: string; lateFee: number; interest: number; other: number; otherDescription: string }[]>([]);
  const [globalOtherDescription, setGlobalOtherDescription] = useState("");
  const [globalOtherAccountId, setGlobalOtherAccountId] = useState("");
  const [globalBillToAssociation, setGlobalBillToAssociation] = useState(false);
  const [chargeDate, setChargeDate] = useState<Date>(new Date());
  const [interestRates, setInterestRates] = useState<Record<string, number>>({});
  const [lateFeeAmounts, setLateFeeAmounts] = useState<Record<string, number>>({});
  const [chartAccounts, setChartAccounts] = useState<ChartAccount[]>([]);
  const ownerLedgerEntriesRef = useRef<Record<string, any[]>>({});

  useEffect(() => { fetchData(); }, []);

  // Compute an owner-group balance as of a given date (yyyy-MM-dd).
  // Includes entries with no date (treated as historical/opening) plus any entry on or before the date.
  const computeBalanceAsOf = (ownerIds: string[], dateStr: string) => {
    return ownerIds.reduce((sum, ownerId) => {
      const entries = ownerLedgerEntriesRef.current[ownerId] || [];
      return sum + entries.reduce((s: number, e: any) => {
        if (e.date && e.date > dateStr) return s;
        return s + (Number(e.debit) || 0) - (Number(e.credit) || 0);
      }, 0);
    }, 0);
  };

  // Recompute each row's balance whenever the charge date changes.
  useEffect(() => {
    if (!rows.length) return;
    const dateStr = format(chargeDate, "yyyy-MM-dd");
    setRows((prev) => prev.map((r) => ({ ...r, balance: computeBalanceAsOf(r.ownerIds, dateStr) })));
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [chargeDate]);

  const fetchData = async () => {
    setLoading(true);
    try {
      // Fetch all active owners with pagination
      const allOwners: any[] = [];
      let from = 0;
      const pageSize = 1000;
      while (true) {
        const { data, error } = await supabase
          .from("owners")
          .select("id, first_name, last_name, association_id, unit_id, units(id, unit_number, address), associations(id, name)")
          .eq("status", "active")
          .order("last_name")
          .range(from, from + pageSize - 1);
        if (error) throw error;
        allOwners.push(...(data || []));
        if (!data || data.length < pageSize) break;
        from += pageSize;
      }

      // Fetch all ledger entries with pagination
      const allLedger: any[] = [];
      from = 0;
      while (true) {
        const { data, error } = await supabase
          .from("owner_ledger_entries")
          .select("id, owner_id, transaction_type, description, debit, credit, date, created_at")
          .range(from, from + pageSize - 1);
        if (error) throw error;
        allLedger.push(...(data || []));
        if (!data || data.length < pageSize) break;
        from += pageSize;
      }

      const { data: assocData } = await supabase.from("associations").select("id, name").eq("status", "active").order("name");
      setAssociations(assocData || []);

      // Fetch chart of accounts for "Other" account selector
      const { data: coaData } = await supabase
        .from("chart_of_accounts")
        .select("id, account_number, account_name")
        .eq("is_active", true)
        .order("account_number");
      setChartAccounts((coaData || []) as ChartAccount[]);

      // Fetch fee rules (interest rates) per association
      const { data: feeRulesData } = await supabase.from("association_fee_rules").select("association_id, interest_rate, late_fee_amount, late_fee_enabled");
      const rateMap: Record<string, number> = {};
      const lateFeeMap: Record<string, number> = {};
      (feeRulesData || []).forEach((r: any) => {
        rateMap[r.association_id] = r.interest_rate || 0;
        if (r.late_fee_enabled && r.late_fee_amount) lateFeeMap[r.association_id] = Number(r.late_fee_amount);
      });
      setInterestRates(rateMap);
      setLateFeeAmounts(lateFeeMap);

      // Fetch unit fee exclusions
      const { data: exclusionData } = await supabase
        .from("unit_fee_exclusions")
        .select("unit_id, fee_type, mode, override_amount, override_percent");
      const exclusionByUnit: Record<string, { late_fee?: FeeExclusion; interest?: FeeExclusion }> = {};
      (exclusionData || []).forEach((e: any) => {
        if (!e.unit_id) return;
        if (!exclusionByUnit[e.unit_id]) exclusionByUnit[e.unit_id] = {};
        exclusionByUnit[e.unit_id][e.fee_type as "late_fee" | "interest"] = {
          fee_type: e.fee_type,
          mode: e.mode,
          override_amount: e.override_amount,
          override_percent: e.override_percent,
        };
      });

      // Build per-owner aggregates
      const ownerAgg = new Map<string, { balance: number; lastPayment: string | null; lastInterest: string | null; lastLateFee: string | null }>();
      const ownerEntries: Record<string, any[]> = {};
      allLedger.forEach((e: any) => {
        if (!e.owner_id) return;

        if (!ownerEntries[e.owner_id]) ownerEntries[e.owner_id] = [];
        ownerEntries[e.owner_id].push(e);

        if (!ownerAgg.has(e.owner_id)) {
          ownerAgg.set(e.owner_id, { balance: 0, lastPayment: null, lastInterest: null, lastLateFee: null });
        }
        const agg = ownerAgg.get(e.owner_id)!;
        agg.balance += (e.debit || 0) - (e.credit || 0);
        const t = (e.transaction_type || "").toLowerCase();
        const d = e.date || null;
        if (d) {
          if (t.includes("payment") || (e.credit > 0 && !t)) {
            if (!agg.lastPayment || d > agg.lastPayment) agg.lastPayment = d;
          }
          if (t.includes("interest")) {
            if (!agg.lastInterest || d > agg.lastInterest) agg.lastInterest = d;
          }
          if (t.includes("late")) {
            if (!agg.lastLateFee || d > agg.lastLateFee) agg.lastLateFee = d;
          }
        }
      });
      ownerLedgerEntriesRef.current = ownerEntries;

      // Group by unit
      const unitGroupMap = new Map<string, any[]>();
      allOwners.forEach((o: any) => {
        const key = o.unit_id || `owner-${o.id}`;
        if (!unitGroupMap.has(key)) unitGroupMap.set(key, []);
        unitGroupMap.get(key)!.push(o);
      });

      const mapped: AccountRow[] = [];
      unitGroupMap.forEach((groupOwners, key) => {
        const first = groupOwners[0];
        const unit = first.units as any;
        const assoc = first.associations as any;

        let balance = 0;
        let lastPayment: string | null = null;
        let lastInterest: string | null = null;
        let lastLateFee: string | null = null;

        const chargeDateStr = format(chargeDate, "yyyy-MM-dd");
        groupOwners.forEach((o: any) => {
          const agg = ownerAgg.get(o.id);
          if (agg) {
            if (agg.lastPayment && (!lastPayment || agg.lastPayment > lastPayment)) lastPayment = agg.lastPayment;
            if (agg.lastInterest && (!lastInterest || agg.lastInterest > lastInterest)) lastInterest = agg.lastInterest;
            if (agg.lastLateFee && (!lastLateFee || agg.lastLateFee > lastLateFee)) lastLateFee = agg.lastLateFee;
          }
        });
        // Balance as of the selected charge date
        balance = groupOwners.reduce((sum: number, o: any) => {
          const entries = ownerEntries[o.id] || [];
          return sum + entries.reduce((s: number, e: any) => {
            if (e.date && e.date > chargeDateStr) return s;
            return s + (Number(e.debit) || 0) - (Number(e.credit) || 0);
          }, 0);
        }, 0);

        mapped.push({
          unitKey: key,
          ownerIds: groupOwners.map((o: any) => o.id),
          ownerNames: groupOwners.map((o: any) => `${o.last_name}, ${o.first_name}`),
          unitId: first.unit_id,
          unitNumber: unit?.unit_number || "—",
          address: unit?.address || "—",
          associationId: first.association_id,
          associationName: assoc?.name || "Unknown",
          balance,
          assessmentBalance: 0,
          
          lastPaymentDate: lastPayment,
          lastInterestDate: lastInterest,
          lastLateFeeDate: lastLateFee,
          lateFeeInput: "",
          interestInput: "",
          otherInput: "",
          otherDescription: "",
          otherAccountId: "",
          billToAssociation: false,
          lateFeeExclusion: first.unit_id ? exclusionByUnit[first.unit_id]?.late_fee || null : null,
          interestExclusion: first.unit_id ? exclusionByUnit[first.unit_id]?.interest || null : null,
        });
      });

      setRows(mapped);
    } catch (err: any) {
      toast({ variant: "destructive", title: "Error", description: err.message });
    } finally {
      setLoading(false);
    }
  };

  const filtered = useMemo(() => {
    let result = rows;
    if (selectedAssociation !== "all") {
      result = result.filter((r) => r.associationId === selectedAssociation);
    }
    if (search) {
      const q = search.toLowerCase();
      result = result.filter(
        (r) =>
          r.ownerNames.some((n) => n.toLowerCase().includes(q)) ||
          r.unitNumber.toLowerCase().includes(q) ||
          r.address.toLowerCase().includes(q)
      );
    }
    return result.sort((a, b) => b.balance - a.balance);
  }, [rows, selectedAssociation, search]);

  const fmt = (n: number) => {
    const prefix = n < 0 ? "-" : "";
    return `${prefix}$${Math.abs(n).toLocaleString(undefined, { minimumFractionDigits: 2 })}`;
  };

  const fmtDate = (d: string | null) => {
    if (!d) return "—";
    return new Date(d + "T00:00:00").toLocaleDateString();
  };

  const updateField = (unitKey: string, field: "lateFeeInput" | "interestInput" | "otherInput" | "otherDescription" | "otherAccountId", value: string) => {
    setRows((prev) =>
      prev.map((r) => (r.unitKey === unitKey ? { ...r, [field]: value } : r))
    );
  };

  const toggleBillToAssociation = (unitKey: string) => {
    setRows((prev) =>
      prev.map((r) => (r.unitKey === unitKey ? { ...r, billToAssociation: !r.billToAssociation } : r))
    );
  };

  const toggleSelect = (id: string) => {
    setSelected((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  };

  const toggleSelectAll = () => {
    if (selected.size === filtered.length) {
      setSelected(new Set());
    } else {
      setSelected(new Set(filtered.map((r) => r.unitKey)));
    }
  };

  // Returns the actual amount to charge after applying any exclusion. Returns 0 to skip.
  // For interest with override_percent, recalculate from the row's assessment balance.
  const resolveAmount = (input: string, exclusion?: FeeExclusion | null, row?: AccountRow): number => {
    const requested = parseFloat(input);
    if (!(requested > 0)) return 0;
    if (!exclusion) return requested;
    if (exclusion.mode === "waive") return 0;
    if (exclusion.mode === "override_amount") {
      const ov = Number(exclusion.override_amount || 0);
      return ov > 0 ? ov : 0;
    }
    if (exclusion.mode === "override_percent" && exclusion.override_percent != null && row) {
      const overrideRate = Number(exclusion.override_percent) * 100;
      const balance = row.assessmentBalance;
      if (overrideRate <= 0 || balance <= 0) return 0;
      return parseFloat((balance * overrideRate / 100).toFixed(2));
    }
    return requested;
  };

  const handleApplyCharges = async () => {
    const rowsToCharge = filtered.filter(
      (r) =>
        selected.has(r.unitKey) &&
        ((r.balance > 0 &&
          (resolveAmount(r.lateFeeInput, r.lateFeeExclusion) > 0 ||
            resolveAmount(r.interestInput, r.interestExclusion, r) > 0)) ||
          (parseFloat(r.otherInput) > 0 && r.otherDescription.trim()))
    );
    if (rowsToCharge.length === 0) {
      sonnerToast.warning("No charges to apply. Select rows and enter amounts.");
      return;
    }

    setSubmitting(true);
    const dateStr = format(chargeDate, "yyyy-MM-dd");
    let success = 0;
    let errors = 0;
    let waivedCount = 0;
    let overriddenCount = 0;

    try {
      for (const row of rowsToCharge) {
        const ownerId = row.ownerIds[0];
        const inserts: any[] = [];
        const currentBalance = computeBalanceAsOf(row.ownerIds, dateStr);
        const isDelinquent = currentBalance > 0;

        const lateFeeRequested = parseFloat(row.lateFeeInput);
        const lateFee = isDelinquent ? resolveAmount(row.lateFeeInput, row.lateFeeExclusion) : 0;
        if (lateFeeRequested > 0 && row.lateFeeExclusion?.mode === "waive") waivedCount++;
        if (lateFeeRequested > 0 && row.lateFeeExclusion?.mode === "override_amount") overriddenCount++;
        if (lateFee > 0) {
          const desc =
            row.lateFeeExclusion?.mode === "override_amount"
              ? "Late fee (override per unit exclusion)"
              : "Late fee (non-payment penalty)";
          inserts.push({
            association_id: row.associationId,
            owner_id: ownerId,
            unit_id: row.unitId,
            date: dateStr,
            transaction_type: "late_fee",
            description: desc,
            debit: lateFee,
            credit: 0,
            created_by: user?.id || null,
          });
        }

        const interestRequested = parseFloat(row.interestInput);
        const interest = isDelinquent ? resolveAmount(row.interestInput, row.interestExclusion, row) : 0;
        if (interestRequested > 0 && row.interestExclusion?.mode === "waive") waivedCount++;
        if (interestRequested > 0 && (row.interestExclusion?.mode === "override_amount" || row.interestExclusion?.mode === "override_percent")) overriddenCount++;
        if (interest > 0) {
          const annualRate = interestRates[row.associationId] || 0;
          const overridePct =
            row.interestExclusion?.mode === "override_percent" && row.interestExclusion.override_percent != null
              ? Number(row.interestExclusion.override_percent) * 100
              : null;
          const desc =
            row.interestExclusion?.mode === "override_amount"
              ? "Interest (override per unit exclusion)"
              : overridePct != null
              ? `Interest @ ${overridePct}% (per unit override)`
              : `Interest @ ${annualRate}% per annum`;
          inserts.push({
            association_id: row.associationId,
            owner_id: ownerId,
            unit_id: row.unitId,
            date: dateStr,
            transaction_type: "interest",
            description: desc,
            debit: interest,
            credit: 0,
            created_by: user?.id || null,
          });
        }

        const other = parseFloat(row.otherInput);
        if (other > 0 && row.otherDescription.trim()) {
          const desc = row.billToAssociation
            ? `${row.otherDescription.trim()} (Billed to Association – Unit ${row.unitNumber})`
            : row.otherDescription.trim();
          inserts.push({
            association_id: row.associationId,
            owner_id: ownerId,
            unit_id: row.unitId,
            date: dateStr,
            transaction_type: "admin_fee",
            description: desc,
            debit: other,
            credit: 0,
            created_by: user?.id || null,
            ...(row.otherAccountId ? { gl_account_id: row.otherAccountId } : {}),
            bill_to_association: row.billToAssociation,
          });
        }

        if (inserts.length > 0) {
          const { error } = await supabase.from("owner_ledger_entries").insert(inserts);
          if (error) { errors++; console.warn(`Insert error for ${row.unitNumber}: ${error.message}`); }
          else success++;
        }
      }


      if (success > 0) {
        const reportData = rowsToCharge.map((row) => ({
          ownerNames: row.ownerNames,
          unitNumber: row.unitNumber,
          address: row.address,
          associationName: row.associationName,
          lateFee: row.balance > 0 ? resolveAmount(row.lateFeeInput, row.lateFeeExclusion) : 0,
          interest: row.balance > 0 ? resolveAmount(row.interestInput, row.interestExclusion, row) : 0,
          other: parseFloat(row.otherInput) || 0,
          otherDescription: row.otherDescription,
        }));
        setLastApplied(reportData);
        const extras: string[] = [];
        if (waivedCount > 0) extras.push(`${waivedCount} waived by exclusion`);
        if (overriddenCount > 0) extras.push(`${overriddenCount} overridden by exclusion`);
        sonnerToast.success(
          `Applied charges to ${success} unit(s)` + (extras.length ? ` · ${extras.join(", ")}` : "")
        );
      }
      if (errors > 0) sonnerToast.error(`Failed for ${errors} unit(s)`);

      setSelected(new Set());
      fetchData();
    } catch (err: any) {
      sonnerToast.error("Failed to apply charges", { description: err.message });
    } finally {
      setSubmitting(false);
    }
  };

  const generateReport = () => {
    if (lastApplied.length === 0) return;
    const today = new Date();
    const dateStr = today.toLocaleDateString();
    const doc = new jsPDF({ orientation: "portrait", unit: "pt", format: "letter" });
    const pageW = doc.internal.pageSize.getWidth();
    const m = 40;

    doc.setFontSize(16);
    doc.text("Bulk Charges Report", m, m + 10);
    doc.setFontSize(10);
    doc.setTextColor(100);
    doc.text(`Generated: ${dateStr}`, m, m + 26);
    doc.setTextColor(0);

    const totalLateFees = lastApplied.reduce((s, r) => s + r.lateFee, 0);
    const totalInterest = lastApplied.reduce((s, r) => s + r.interest, 0);
    const totalOther = lastApplied.reduce((s, r) => s + r.other, 0);

    doc.setFontSize(11);
    doc.text(`Total Late Fees: $${totalLateFees.toFixed(2)}`, m, m + 48);
    doc.text(`Total Interest: $${totalInterest.toFixed(2)}`, m + 180, m + 48);
    doc.text(`Total Other: $${totalOther.toFixed(2)}`, m + 340, m + 48);
    doc.text(`Accounts: ${lastApplied.length}`, m + 480, m + 48);

    const tableRows = lastApplied.map((r) => [
      r.ownerNames.join("; "),
      r.unitNumber,
      r.associationName,
      r.lateFee > 0 ? `$${r.lateFee.toFixed(2)}` : "—",
      r.interest > 0 ? `$${r.interest.toFixed(2)}` : "—",
      r.other > 0 ? `$${r.other.toFixed(2)}` : "—",
      r.other > 0 ? r.otherDescription : "",
      `$${(r.lateFee + r.interest + r.other).toFixed(2)}`,
    ]);

    autoTable(doc, {
      startY: m + 62,
      margin: { left: m, right: m },
      head: [["Owner", "Unit", "Association", "Late Fee", "Interest", "Other", "Description", "Total"]],
      body: tableRows,
      styles: { fontSize: 7, cellPadding: 3 },
      headStyles: { fillColor: [51, 51, 51], textColor: 255, fontStyle: "bold" },
      alternateRowStyles: { fillColor: [245, 245, 245] },
    });

    doc.save(`bulk-charges-report-${today.toISOString().split("T")[0]}.pdf`);
  };

  const handleFillLateFees = (amount: string) => {
    setRows((prev) =>
      prev.map((r) => (selected.has(r.unitKey) ? { ...r, lateFeeInput: r.balance > 0 ? amount : "" } : r))
    );
  };

  const handleCalcAssessmentBalances = () => {
    const chargeDateStr = format(chargeDate, "yyyy-MM-dd");
    setRows((prev) =>
      prev.map((r) => {
        if (!selected.has(r.unitKey)) return r;
        const currentBalance = computeBalanceAsOf(r.ownerIds, chargeDateStr);
        if (currentBalance <= 0) return { ...r, assessmentBalance: 0 };
        const assessmentBalance = r.ownerIds.reduce((sum, ownerId) => {
          const entries = (ownerLedgerEntriesRef.current[ownerId] || []).filter(
            (entry: any) => !entry.date || entry.date <= chargeDateStr
          );
          return sum + calculateRemainingUnpaidAssessmentBalance(entries);
        }, 0);
        return { ...r, assessmentBalance: Math.max(0, Math.min(assessmentBalance, currentBalance)) };
      })
    );
  };

  const handleFillInterest = (amount: string) => {
    setRows((prev) =>
      prev.map((r) => (selected.has(r.unitKey) ? { ...r, interestInput: amount } : r))
    );
  };

  const handleAutoFillInterest = () => {
    const chargeDateStr = format(chargeDate, "yyyy-MM-dd");

    setRows((prev) =>
      prev.map((r) => {
        if (!selected.has(r.unitKey)) return r;
        const currentBalance = computeBalanceAsOf(r.ownerIds, chargeDateStr);
        const isDelinquent = currentBalance > 0;

        const exclusion = r.interestExclusion;

        if (!isDelinquent) {
          return { ...r, assessmentBalance: 0, interestInput: "" };
        }

        // If waived, skip entirely
        if (exclusion?.mode === "waive") {
          return { ...r, assessmentBalance: r.assessmentBalance, interestInput: "0" };
        }

        const baseRate = interestRates[r.associationId] || 0;
        // Per-unit % override (stored as fraction, e.g. 0.015 for 1.5%); convert to whole-percent
        const overridePctRate =
          exclusion?.mode === "override_percent" && exclusion.override_percent != null
            ? Number(exclusion.override_percent) * 100
            : null;
        const rate = overridePctRate != null ? overridePctRate : baseRate;
        const rawAssessmentBalance = r.ownerIds.reduce((sum, ownerId) => {
          const entries = (ownerLedgerEntriesRef.current[ownerId] || []).filter(
            (entry: any) => !entry.date || entry.date <= chargeDateStr
          );

          return sum + calculateRemainingUnpaidAssessmentBalance(entries);
        }, 0);
        // Cap interest base at the owner's total balance — credits / overpaid
        // fee buckets should reduce the principal interest is charged on.
        const assessmentBalance = Math.max(0, Math.min(rawAssessmentBalance, currentBalance));

        if (rate <= 0 || assessmentBalance <= 0) {
          return { ...r, assessmentBalance, interestInput: "" };
        }

        let interest = parseFloat((assessmentBalance * rate / 100).toFixed(2));

        // If override_amount, cap to the override value
        if (exclusion?.mode === "override_amount") {
          const ov = Number(exclusion.override_amount || 0);
          interest = ov > 0 ? ov : 0;
        }

        return { ...r, assessmentBalance, interestInput: interest > 0 ? interest.toString() : "" };
      })
    );
  };

  const handleFillOther = (amount: string) => {
    setRows((prev) =>
      prev.map((r) => (selected.has(r.unitKey) ? {
        ...r,
        otherInput: amount,
        otherDescription: r.otherDescription || globalOtherDescription,
        otherAccountId: r.otherAccountId || globalOtherAccountId,
        billToAssociation: globalBillToAssociation || r.billToAssociation,
      } : r))
    );
  };

  const handleFillOtherDescription = () => {
    if (!globalOtherDescription.trim()) return;
    setRows((prev) =>
      prev.map((r) => (selected.has(r.unitKey) ? {
        ...r,
        otherDescription: globalOtherDescription,
        otherAccountId: globalOtherAccountId || r.otherAccountId,
        billToAssociation: globalBillToAssociation || r.billToAssociation,
      } : r))
    );
  };

  const handleRunNow = async () => {
    // Select all filtered rows
    const allKeys = new Set(filtered.map((r) => r.unitKey));
    setSelected(allKeys);

    // Auto-fill late fees from association fee rules
    const chargeDateStr = format(chargeDate, "yyyy-MM-dd");
    setRows((prev) =>
      prev.map((r) => {
        if (!allKeys.has(r.unitKey)) return r;
        const currentBalance = computeBalanceAsOf(r.ownerIds, chargeDateStr);
        const isDelinquent = currentBalance > 0;

        // Late fee
        let lateFeeVal = "";
        if (!isDelinquent) {
          lateFeeVal = "";
        } else if (r.lateFeeExclusion?.mode === "waive") {
          lateFeeVal = "0";
        } else if (r.lateFeeExclusion?.mode === "override_amount" && r.lateFeeExclusion.override_amount != null) {
          lateFeeVal = Number(r.lateFeeExclusion.override_amount).toString();
        } else {
          const assocLateFee = lateFeeAmounts[r.associationId];
          lateFeeVal = assocLateFee ? assocLateFee.toString() : "";
        }

        // Interest (same logic as handleAutoFillInterest)
        let interestVal = "";
        const exclusion = r.interestExclusion;
        const rawAssessmentBalance = r.ownerIds.reduce((sum, ownerId) => {
          const entries = (ownerLedgerEntriesRef.current[ownerId] || []).filter(
            (entry: any) => !entry.date || entry.date <= chargeDateStr
          );
          return sum + calculateRemainingUnpaidAssessmentBalance(entries);
        }, 0);
        // Cap interest base at the owner's total balance.
        const updatedAssessmentBalance = isDelinquent ? Math.max(0, Math.min(rawAssessmentBalance, currentBalance)) : 0;

        if (!isDelinquent) {
          interestVal = "";
        } else if (exclusion?.mode === "waive") {
          interestVal = "0";
        } else {
          const baseRate = interestRates[r.associationId] || 0;
          const overridePctRate =
            exclusion?.mode === "override_percent" && exclusion.override_percent != null
              ? Number(exclusion.override_percent) * 100
              : null;
          const rate = overridePctRate != null ? overridePctRate : baseRate;

          if (rate > 0 && updatedAssessmentBalance > 0) {
            let interest = parseFloat((updatedAssessmentBalance * rate / 100).toFixed(2));
            if (exclusion?.mode === "override_amount") {
              const ov = Number(exclusion.override_amount || 0);
              interest = ov > 0 ? ov : 0;
            }
            interestVal = interest > 0 ? interest.toString() : "";
          }
        }

        return { ...r, lateFeeInput: lateFeeVal, interestInput: interestVal, assessmentBalance: updatedAssessmentBalance };
      })
    );
  };

  const selectedCount = selected.size;
  const chargeableCount = filtered.filter(
    (r) => selected.has(r.unitKey) && ((r.balance > 0 && (parseFloat(r.lateFeeInput) > 0 || parseFloat(r.interestInput) > 0)) || (parseFloat(r.otherInput) > 0 && r.otherDescription.trim()))
  ).length;

  return (
    <div className="space-y-6 p-6">
      <div className="flex items-center justify-between">
        <div>
          <h1 className="text-2xl font-bold tracking-tight">Bulk Charges</h1>
          <p className="text-sm text-muted-foreground">
            Review account balances and apply missed assessments or late fees in bulk.
          </p>
        </div>
        <div className="flex items-center gap-2">
          <Button variant="secondary" onClick={handleRunNow} disabled={submitting || loading || filtered.length === 0}>
            <Zap className="mr-2 h-4 w-4" />
            Run Now
          </Button>
          {lastApplied.length > 0 && (
            <Button variant="outline" onClick={generateReport}>
              <FileText className="mr-2 h-4 w-4" />
              Download Report
            </Button>
          )}
          <Button onClick={handleApplyCharges} disabled={submitting || chargeableCount === 0}>
            {submitting ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Send className="mr-2 h-4 w-4" />}
            Apply Charges ({chargeableCount})
          </Button>
        </div>
      </div>

      <div className="flex flex-wrap items-center gap-3">
        <Select value={selectedAssociation} onValueChange={setSelectedAssociation}>
          <SelectTrigger className="w-[280px]">
            <SelectValue placeholder="All Associations" />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="all">All Associations</SelectItem>
            {associations.map((a) => (
              <SelectItem key={a.id} value={a.id}>{a.name}</SelectItem>
            ))}
          </SelectContent>
        </Select>

        <Popover>
          <PopoverTrigger asChild>
            <Button variant="outline" className={cn("w-[180px] justify-start text-left font-normal", !chargeDate && "text-muted-foreground")}>
              <CalendarIcon className="mr-2 h-4 w-4" />
              {chargeDate ? format(chargeDate, "MMM d, yyyy") : "Charge date"}
            </Button>
          </PopoverTrigger>
          <PopoverContent className="w-auto p-0" align="start">
            <Calendar mode="single" selected={chargeDate} onSelect={(d) => d && setChargeDate(d)} initialFocus className={cn("p-3 pointer-events-auto")} />
          </PopoverContent>
        </Popover>

        <div className="relative flex-1 min-w-[200px]">
          <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
          <Input
            placeholder="Search owner, unit, address..."
            value={search}
            onChange={(e) => setSearch(e.target.value)}
            className="pl-9"
          />
        </div>

        {selectedCount > 0 && (
          <div className="flex items-center gap-2 flex-wrap">
            <Button size="sm" variant="outline" onClick={() => handleFillLateFees("25.00")}>
              Fill Late $25
            </Button>
            <Button size="sm" variant="outline" onClick={() => handleFillLateFees("50.00")}>
              Fill Late $50
            </Button>
            <Button size="sm" variant="outline" onClick={handleCalcAssessmentBalances}>
              Calc Balances
            </Button>
            <Button size="sm" variant="outline" onClick={handleAutoFillInterest}>
              Auto-Fill Interest
            </Button>
            <Button size="sm" variant="outline" onClick={() => handleFillInterest("10.00")}>
              Fill Interest $10
            </Button>
            <div className="flex items-center gap-1 flex-wrap">
              <Input
                placeholder="Other description..."
                value={globalOtherDescription}
                onChange={(e) => setGlobalOtherDescription(e.target.value)}
                className="w-[180px] h-8 text-sm"
              />
              <Select value={globalOtherAccountId || "none"} onValueChange={(v) => setGlobalOtherAccountId(v === "none" ? "" : v)}>
                <SelectTrigger className="w-[200px] h-8 text-sm">
                  <SelectValue placeholder="Account (Default)" />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="none">Default Account</SelectItem>
                  {chartAccounts.map((a) => (
                    <SelectItem key={a.id} value={a.id}>
                      {a.account_number} - {a.account_name}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
              <div className="flex items-center gap-1.5">
                <Checkbox
                  id="global-bill-assoc"
                  checked={globalBillToAssociation}
                  onCheckedChange={(v) => setGlobalBillToAssociation(!!v)}
                />
                <label htmlFor="global-bill-assoc" className="text-xs cursor-pointer whitespace-nowrap">Bill Assoc.</label>
              </div>
              <Button size="sm" variant="outline" onClick={handleFillOtherDescription} disabled={!globalOtherDescription.trim()}>
                Set Desc
              </Button>
              <Button size="sm" variant="outline" onClick={() => handleFillOther("25.00")}>
                Fill Other $25
              </Button>
            </div>
          </div>
        )}
      </div>

      <Card>
        <CardContent className="p-0">
          {loading ? (
            <div className="flex items-center justify-center py-16">
              <Loader2 className="h-8 w-8 animate-spin text-primary" />
            </div>
          ) : (
            <div className="overflow-auto max-h-[calc(100vh-280px)]">
              <Table>
                <TableHeader>
                  <TableRow>
                    <TableHead className="w-10">
                      <Checkbox
                        checked={selectedCount === filtered.length && filtered.length > 0}
                        onCheckedChange={toggleSelectAll}
                      />
                    </TableHead>
                    <TableHead>Owner / Unit</TableHead>
                    <TableHead>Association</TableHead>
                    <TableHead className="text-right">Balance</TableHead>
                     <TableHead className="text-right">Assess. Balance</TableHead>
                    <TableHead>Last Payment</TableHead>
                    <TableHead>Last Interest</TableHead>
                    <TableHead>Last Late Fee</TableHead>
                    <TableHead className="text-right w-[120px]">Late Fee $</TableHead>
                    <TableHead className="text-right w-[120px]">Interest $</TableHead>
                    <TableHead className="text-right w-[120px]">Other $</TableHead>
                    <TableHead className="w-[160px]">Other Desc.</TableHead>
                    <TableHead className="w-[180px]">Account</TableHead>
                    <TableHead className="w-[80px] text-center">Bill Assoc.</TableHead>
                  </TableRow>
                </TableHeader>
                <TableBody>
                  {filtered.length === 0 ? (
                    <TableRow>
                      <TableCell colSpan={14} className="text-center text-muted-foreground py-8">
                        No accounts found.
                      </TableCell>
                    </TableRow>
                  ) : (
                    filtered.map((row) => (
                      <TableRow key={row.unitKey} className={selected.has(row.unitKey) ? "bg-muted/30" : ""}>
                        <TableCell>
                          <Checkbox
                            checked={selected.has(row.unitKey)}
                            onCheckedChange={() => toggleSelect(row.unitKey)}
                          />
                        </TableCell>
                        <TableCell>
                          <div className="font-medium text-sm">{row.ownerNames.join("; ")}</div>
                          <div className="text-xs text-muted-foreground">{row.unitNumber} — {row.address}</div>
                        </TableCell>
                        <TableCell className="text-sm">{row.associationName}</TableCell>
                        <TableCell className={`text-right font-mono text-sm ${row.balance > 0 ? "text-destructive" : row.balance < 0 ? "text-emerald-600" : ""}`}>
                          {fmt(row.balance)}
                        </TableCell>
                        <TableCell className={`text-right font-mono text-sm ${row.assessmentBalance > 0 ? "text-destructive" : ""}`}>
                          {fmt(row.assessmentBalance)}
                        </TableCell>
                        <TableCell className="text-sm">{fmtDate(row.lastPaymentDate)}</TableCell>
                        <TableCell className="text-sm">{fmtDate(row.lastInterestDate)}</TableCell>
                        <TableCell className="text-sm">{fmtDate(row.lastLateFeeDate)}</TableCell>
                        <TableCell>
                          <div className="flex flex-col gap-1">
                            <Input
                              type="number"
                              step="0.01"
                              min="0"
                              placeholder="0.00"
                              value={row.lateFeeInput}
                              onChange={(e) => updateField(row.unitKey, "lateFeeInput", e.target.value)}
                              disabled={row.lateFeeExclusion?.mode === "waive"}
                              className="w-[100px] text-right text-sm h-8"
                            />
                            {row.lateFeeExclusion?.mode === "waive" && (
                              <Badge variant="destructive" className="gap-1 text-[10px] py-0 px-1.5 w-fit">
                                <Ban className="w-3 h-3" /> Waived
                              </Badge>
                            )}
                            {row.lateFeeExclusion?.mode === "override_amount" && (
                              <Badge variant="secondary" className="gap-1 text-[10px] py-0 px-1.5 w-fit">
                                <ShieldOff className="w-3 h-3" /> →${Number(row.lateFeeExclusion.override_amount || 0).toFixed(2)}
                              </Badge>
                            )}
                          </div>
                        </TableCell>
                        <TableCell>
                          <div className="flex flex-col gap-1">
                            <Input
                              type="number"
                              step="0.01"
                              min="0"
                              placeholder="0.00"
                              value={row.interestInput}
                              onChange={(e) => updateField(row.unitKey, "interestInput", e.target.value)}
                              disabled={row.interestExclusion?.mode === "waive"}
                              className="w-[100px] text-right text-sm h-8"
                            />
                            {row.interestExclusion?.mode === "waive" && (
                              <Badge variant="destructive" className="gap-1 text-[10px] py-0 px-1.5 w-fit">
                                <Ban className="w-3 h-3" /> Waived
                              </Badge>
                            )}
                            {row.interestExclusion?.mode === "override_amount" && (
                              <Badge variant="secondary" className="gap-1 text-[10px] py-0 px-1.5 w-fit">
                                <ShieldOff className="w-3 h-3" /> →${Number(row.interestExclusion.override_amount || 0).toFixed(2)}
                              </Badge>
                            )}
                            {row.interestExclusion?.mode === "override_percent" && (
                              <Badge variant="secondary" className="gap-1 text-[10px] py-0 px-1.5 w-fit">
                                <ShieldOff className="w-3 h-3" /> @{(Number(row.interestExclusion.override_percent || 0) * 100).toFixed(4).replace(/\.?0+$/, "")}%
                              </Badge>
                            )}
                          </div>
                        </TableCell>
                        <TableCell>
                          <Input
                            type="number"
                            step="0.01"
                            min="0"
                            placeholder="0.00"
                            value={row.otherInput}
                            onChange={(e) => updateField(row.unitKey, "otherInput", e.target.value)}
                            className="w-[100px] text-right text-sm h-8"
                          />
                        </TableCell>
                        <TableCell>
                          <Input
                            placeholder="Description..."
                            value={row.otherDescription}
                            onChange={(e) => updateField(row.unitKey, "otherDescription", e.target.value)}
                            className="w-[150px] text-sm h-8"
                          />
                        </TableCell>
                        <TableCell className="text-xs text-muted-foreground truncate max-w-[170px]">
                          {row.otherAccountId
                            ? chartAccounts.find((a) => a.id === row.otherAccountId)
                              ? `${chartAccounts.find((a) => a.id === row.otherAccountId)!.account_number} - ${chartAccounts.find((a) => a.id === row.otherAccountId)!.account_name}`
                              : "Custom"
                            : "Default"}
                        </TableCell>
                        <TableCell className="text-center">
                          <Checkbox
                            checked={row.billToAssociation}
                            onCheckedChange={() => toggleBillToAssociation(row.unitKey)}
                          />
                        </TableCell>
                      </TableRow>
                    ))
                  )}
                </TableBody>
              </Table>
            </div>
          )}
        </CardContent>
      </Card>

      <div className="text-xs text-muted-foreground">
        Showing {filtered.length} account(s) · {selectedCount} selected · {chargeableCount} with charges to apply
      </div>
    </div>
  );
}