import React, { useEffect, useState, useCallback } from "react";
import { useSearchParams } from "react-router-dom";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Badge } from "@/components/ui/badge";
import {
    Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger,
} from "@/components/ui/dialog";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Plus, Trash2, Pencil, Search, Phone, MapPin, Upload, FileDown, CheckCircle2, AlertTriangle, Loader2, Gift, CheckSquare, Square, X as CloseIcon, Undo2 } from "lucide-react";
import { toast } from "sonner";
import { api, formatApiError } from "@/lib/api";
import { cacheGet, cacheSet, buildKey } from "@/lib/offlineCache";
import { makeShareDraggable } from "@/lib/shareDrag";
import { useCompany } from "@/context/CompanyContext";
import { useAuth } from "@/context/AuthContext";
import { formatINR } from "@/lib/format";
import { ImageUploader } from "@/components/ImageUploader";
import { useRefreshSubscriber } from "@/context/RefreshContext";
import { safeDelete } from "@/lib/safeDelete";
import { LoyaltyDialog } from "@/components/LoyaltyDialog";
import { MapPicker } from "@/components/maps/MapPicker";
import { openInGoogleMaps } from "@/components/maps/mapHelpers";
import GstinVerifyField from "@/components/GstinVerifyField";
import { Checkbox } from "@/components/ui/checkbox";
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";

const empty = { name: "", type: "customer", gstin: "", phone: "", email: "", address: "", state: "", opening_balance: 0, credit_limit: 0, photo_url: "", latitude: null, longitude: null };

// Lightweight client-side validation — does NOT block save on optional fields, only flags obvious issues.
function validateForm(form) {
    const errors = {};
    const name = (form.name || "").trim();
    if (!name) errors.name = "Name is required";
    else if (name.length < 2) errors.name = "Name must be at least 2 characters";
    if (form.gstin && form.gstin.trim() && !/^[0-9A-Z]{15}$/i.test(form.gstin.trim())) {
        errors.gstin = "GSTIN must be 15 characters (alphanumeric)";
    }
    if (form.email && form.email.trim() && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email.trim())) {
        errors.email = "Invalid email address";
    }
    if (form.phone && form.phone.trim() && !/^[0-9+\-\s()]{6,16}$/.test(form.phone.trim())) {
        errors.phone = "Invalid phone number";
    }
    return errors;
}

export default function Parties() {
    const { activeId } = useCompany();
    const { isAdmin } = useAuth();
    const [parties, setParties] = useState([]);
    const [loading, setLoading] = useState(true);
    const [open, setOpen] = useState(false);
    const [form, setForm] = useState(empty);
    const [editing, setEditing] = useState(null);
    const [loyaltyParty, setLoyaltyParty] = useState(null);
    const [tab, setTab] = useState("all");
    const [q, setQ] = useState("");
    const [searchParams, setSearchParams] = useSearchParams();
    const [saving, setSaving] = useState(false);            // prevents duplicate submits + drives spinner
    const [fieldErrors, setFieldErrors] = useState({});     // inline per-field validation messages
    const [mapPickerOpen, setMapPickerOpen] = useState(false);

    // Bulk selection state
    const [selectMode, setSelectMode] = useState(false);
    const [selected, setSelected] = useState(() => new Set());  // party ids
    const [bulkConfirmOpen, setBulkConfirmOpen] = useState(false);
    const [bulkDeleting, setBulkDeleting] = useState(false);
    const [lastDeletedIds, setLastDeletedIds] = useState(null);  // for undo
    const undoTimerRef = React.useRef(null);

    const toggleOne = useCallback((id) => {
        setSelected((prev) => {
            const next = new Set(prev);
            if (next.has(id)) next.delete(id); else next.add(id);
            return next;
        });
    }, []);
    const toggleAllVisible = useCallback((rows) => {
        setSelected((prev) => {
            const allSelected = rows.every((r) => prev.has(r.id));
            const next = new Set(prev);
            if (allSelected) rows.forEach((r) => next.delete(r.id));
            else rows.forEach((r) => next.add(r.id));
            return next;
        });
    }, []);
    const clearSelection = useCallback(() => setSelected(new Set()), []);
    const exitSelectMode = useCallback(() => {
        setSelectMode(false);
        clearSelection();
    }, [clearSelection]);

    // Auto-open Add Party when ?new=1
    useEffect(() => {
        if (searchParams.get("new") === "1") {
            setForm(empty);
            setEditing(null);
            setFieldErrors({});
            setOpen(true);
            searchParams.delete("new");
            setSearchParams(searchParams, { replace: true });
        }
    }, [searchParams, setSearchParams]);

    const load = useCallback(async () => {
        if (!activeId) return;
        const key = buildKey("/parties", { company_id: activeId });
        // v12.30 — offline-first: paint from IDB cache instantly, then revalidate
        try {
            const cached = await cacheGet(key);
            if (cached) { setParties(cached); setLoading(false); }
            else setLoading(true);
        } catch { setLoading(true); }
        try {
            const { data } = await api.get("/parties", { params: { company_id: activeId } });
            setParties(data);
            if (data) cacheSet(key, data, { ttlMs: 5 * 60_000, company: activeId }).catch(() => { /* ignore */ });
        } catch (e) {
            if (!e?.isOffline && !e?.silent) {
                console.error("[Parties] load failed:", e);
                toast.error(formatApiError(e));
            }
        } finally { setLoading(false); }
    }, [activeId]);
    useEffect(() => { load(); }, [load]);
    useRefreshSubscriber(load);

    const submit = async () => {
        if (saving) return;  // hard guard against double-click

        // 1. Active company required
        if (!activeId) {
            toast.error("Please select an active company before adding a party.");
            return;
        }

        // 2. Client-side validation
        const errors = validateForm(form);
        if (Object.keys(errors).length) {
            setFieldErrors(errors);
            const first = Object.values(errors)[0];
            toast.error(first);
            return;
        }
        setFieldErrors({});

        // 3. Build clean payload — trim strings, coerce numbers, drop empty optionals safely
        const payload = {
            name: (form.name || "").trim(),
            type: form.type || "customer",
            gstin: (form.gstin || "").trim().toUpperCase(),
            phone: (form.phone || "").trim(),
            email: (form.email || "").trim(),
            address: (form.address || "").trim(),
            state: (form.state || "").trim(),
            opening_balance: Number.isFinite(parseFloat(form.opening_balance)) ? parseFloat(form.opening_balance) : 0,
            credit_limit: Number.isFinite(parseFloat(form.credit_limit)) ? parseFloat(form.credit_limit) : 0,
            photo_url: form.photo_url || "",
            latitude: form.latitude ?? null,
            longitude: form.longitude ?? null,
        };

        setSaving(true);
        console.debug("[Parties] submit", { editing, activeId, payload });
        try {
            let saved;
            if (editing) {
                const res = await api.put(`/parties/${editing}`, payload);
                saved = res.data;
                toast.success(`${payload.name} updated successfully`);
            } else {
                const res = await api.post(`/parties`, payload, { params: { company_id: activeId } });
                saved = res.data;
                toast.success(`${payload.name} added successfully`);
            }
            console.debug("[Parties] saved:", saved);
            setOpen(false);
            setForm(empty);
            setEditing(null);
            setFieldErrors({});
            await load();
        } catch (e) {
            console.error("[Parties] submit failed:", e?.response?.status, e?.response?.data, e);
            toast.error(formatApiError(e));
        } finally {
            setSaving(false);
        }
    };

    const remove = async (row) => {
        await safeDelete("party", row, { onSuccess: load });
    };

    // -------- Bulk delete --------
    const performBulkDelete = useCallback(async () => {
        const ids = Array.from(selected);
        if (ids.length === 0) return;
        setBulkDeleting(true);
        try {
            const { data } = await api.post("/parties/bulk-delete", { ids });
            const moved = data?.moved ?? 0;
            const skipped = data?.skipped ?? 0;
            // Snapshot for Undo — store the ids that were actually moved
            setLastDeletedIds(ids);
            if (undoTimerRef.current) clearTimeout(undoTimerRef.current);
            undoTimerRef.current = setTimeout(() => setLastDeletedIds(null), 10000);
            // Optimistic local removal
            setParties((prev) => prev.filter((p) => !selected.has(p.id)));
            clearSelection();
            setBulkConfirmOpen(false);
            setSelectMode(false);
            toast.success(
                `${moved} parties moved to Trash${skipped > 0 ? ` (${skipped} skipped)` : ""}`,
                {
                    duration: 9000,
                    action: {
                        label: "Undo",
                        onClick: () => undoBulkDelete(ids),
                    },
                }
            );
        } catch (e) {
            toast.error(formatApiError(e) || "Bulk delete failed");
        } finally {
            setBulkDeleting(false);
        }
    }, [selected, clearSelection]);

    const undoBulkDelete = useCallback(async (ids) => {
        if (!ids || ids.length === 0) return;
        try {
            const { data } = await api.post("/parties/bulk-restore", { ids });
            toast.success(`${data?.restored ?? ids.length} parties restored`);
            setLastDeletedIds(null);
            await load();
        } catch (e) {
            toast.error(formatApiError(e) || "Restore failed");
        }
    }, [load]);

    // Allow Enter inside the form to trigger save (skip if focus is on a button or textarea)
    const onKeyDown = (e) => {
        if (e.key === "Enter" && !saving) {
            const tag = (e.target?.tagName || "").toLowerCase();
            if (tag !== "textarea" && tag !== "button") {
                e.preventDefault();
                submit();
            }
        }
    };

    const filtered = parties.filter((p) => {
        if (tab !== "all" && p.type !== tab) return false;
        if (q && !p.name.toLowerCase().includes(q.toLowerCase()) && !(p.phone || "").includes(q)) return false;
        return true;
    });

    // Page-level keyboard shortcuts (Ctrl+A select-all in select mode, Delete to trigger bulk delete)
    useEffect(() => {
        if (!selectMode || !isAdmin) return undefined;
        const onPageKey = (e) => {
            // Ignore typing inside form inputs/textareas/dialogs
            const t = (e.target?.tagName || "").toLowerCase();
            if (t === "input" || t === "textarea" || t === "select") return;
            if (open || bulkConfirmOpen) return;  // don't hijack while a dialog is open
            if ((e.ctrlKey || e.metaKey) && (e.key === "a" || e.key === "A")) {
                e.preventDefault();
                toggleAllVisible(filtered);
            } else if (e.key === "Delete" && selected.size > 0) {
                e.preventDefault();
                setBulkConfirmOpen(true);
            } else if (e.key === "Escape") {
                exitSelectMode();
            }
        };
        window.addEventListener("keydown", onPageKey);
        return () => window.removeEventListener("keydown", onPageKey);
    }, [selectMode, isAdmin, filtered, selected, open, bulkConfirmOpen, toggleAllVisible, exitSelectMode]);

    return (
        <div className="space-y-6" data-testid="parties-page">
            <div className="flex flex-wrap items-end justify-between gap-3">
                <div>
                    <div className="label-cap">Masters</div>
                    <h1 className="font-display text-3xl font-bold tracking-tight">Parties</h1>
                    <p className="text-sm text-muted-foreground mt-1">Customers & vendors with GSTIN and credit terms.</p>
                </div>
                {isAdmin && (
                    <div className="flex flex-wrap gap-2 items-center">
                        {/* Bulk-select toolbar — appears only when select mode is ON */}
                        {selectMode ? (
                            <>
                                <Button
                                    variant="outline"
                                    size="sm"
                                    onClick={exitSelectMode}
                                    data-testid="parties-cancel-select-btn"
                                    className="h-9"
                                >
                                    <CloseIcon className="h-4 w-4 mr-1.5" /> Cancel
                                </Button>
                                <div className="px-2.5 py-1 rounded-md bg-primary/10 text-primary text-xs font-semibold" data-testid="parties-selected-count">
                                    Selected: {selected.size}
                                </div>
                                <Button
                                    variant="destructive"
                                    size="sm"
                                    disabled={selected.size === 0 || bulkDeleting}
                                    onClick={() => setBulkConfirmOpen(true)}
                                    data-testid="parties-bulk-delete-btn"
                                    className="h-9"
                                >
                                    {bulkDeleting ? <Loader2 className="h-4 w-4 mr-1.5 animate-spin" /> : <Trash2 className="h-4 w-4 mr-1.5" />}
                                    Delete
                                </Button>
                            </>
                        ) : (
                            <Button
                                variant="outline"
                                size="sm"
                                onClick={() => setSelectMode(true)}
                                data-testid="parties-enter-select-btn"
                                className="h-9"
                                title="Bulk-select to delete multiple parties"
                            >
                                <CheckSquare className="h-4 w-4 mr-1.5" /> Select
                            </Button>
                        )}
                        <ImportPartiesButton activeId={activeId} onDone={load} />
                        <Dialog open={open} onOpenChange={(v) => { setOpen(v); if (!v) { setForm(empty); setEditing(null); setFieldErrors({}); } }}>
                            <DialogTrigger asChild>
                                <Button className="bg-primary hover:bg-primary/90" data-testid="add-party-button"><Plus className="h-4 w-4 mr-1.5" /> Add Party</Button>
                            </DialogTrigger>
                            <DialogContent className="max-w-2xl max-h-[92vh] overflow-y-auto w-[calc(100vw-2rem)]">
                                <DialogHeader><DialogTitle>{editing ? "Edit Party" : "Add Party"}</DialogTitle></DialogHeader>

                            {/* Profile photo — circular thumbnail */}
                            <div className="flex items-center gap-4 pb-3 border-b border-border">
                                <ImageUploader
                                    value={form.photo_url}
                                    onChange={(url) => setForm({ ...form, photo_url: url })}
                                    compact
                                    shape="circle"
                                    size={80}
                                    testidPrefix="party-photo"
                                    maxSizeMB={1}
                                />
                                <div className="flex-1">
                                    <Label className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">Customer / Vendor Photo</Label>
                                    <p className="text-[10px] text-muted-foreground mt-1">Tap to upload — helps identify the party at-a-glance in lists. JPG · PNG · WEBP · ≤ 1 MB</p>
                                </div>
                            </div>

                            <div className="grid grid-cols-1 sm:grid-cols-2 gap-3" onKeyDown={onKeyDown}>
                                <Field label="Name *" error={fieldErrors.name}>
                                    <Input data-testid="party-name-input" autoFocus value={form.name} onChange={(e) => { setForm({ ...form, name: e.target.value }); if (fieldErrors.name) setFieldErrors({ ...fieldErrors, name: undefined }); }} aria-invalid={!!fieldErrors.name} />
                                </Field>
                                <Field label="Type *">
                                    <Select value={form.type} onValueChange={(v) => setForm({ ...form, type: v })}>
                                        <SelectTrigger data-testid="party-type-select"><SelectValue /></SelectTrigger>
                                        <SelectContent>
                                            <SelectItem value="customer">Customer</SelectItem>
                                            <SelectItem value="vendor">Vendor</SelectItem>
                                        </SelectContent>
                                    </Select>
                                </Field>
                                <Field label="GSTIN — verify to auto-fill name & address" error={fieldErrors.gstin} full>
                                    <GstinVerifyField
                                        value={form.gstin}
                                        onChange={(v) => {
                                            setForm({ ...form, gstin: v });
                                            if (fieldErrors.gstin) setFieldErrors({ ...fieldErrors, gstin: undefined });
                                        }}
                                        onVerified={(data) => {
                                            // Auto-fill only when fields are empty (don't overwrite user edits)
                                            setForm((p) => ({
                                                ...p,
                                                name: p.name?.trim() ? p.name : (data.legal_name || data.trade_name || p.name),
                                                state: p.state?.trim() ? p.state : (data.state || data.state_name_from_code || p.state),
                                                address: p.address?.trim() ? p.address : (data.address_line || p.address),
                                            }));
                                        }}
                                        companyId={activeId}
                                        error={fieldErrors.gstin}
                                        testidPrefix="party-gstin"
                                    />
                                </Field>
                                <Field label="State">
                                    <Input data-testid="party-state-input" value={form.state} onChange={(e) => setForm({ ...form, state: e.target.value })} placeholder="e.g. Goa" />
                                </Field>
                                <Field label="Phone" error={fieldErrors.phone}>
                                    <Input data-testid="party-phone-input" value={form.phone} onChange={(e) => { setForm({ ...form, phone: e.target.value }); if (fieldErrors.phone) setFieldErrors({ ...fieldErrors, phone: undefined }); }} placeholder="+91 9876543210" inputMode="tel" aria-invalid={!!fieldErrors.phone} />
                                </Field>
                                <Field label="Email" error={fieldErrors.email}>
                                    <Input data-testid="party-email-input" type="email" value={form.email} onChange={(e) => { setForm({ ...form, email: e.target.value }); if (fieldErrors.email) setFieldErrors({ ...fieldErrors, email: undefined }); }} placeholder="name@example.com" aria-invalid={!!fieldErrors.email} />
                                </Field>
                                <Field label="Address" full>
                                    <Input data-testid="party-address-input" value={form.address} onChange={(e) => setForm({ ...form, address: e.target.value })} />
                                </Field>
                                <Field label="GPS Location" full>
                                    <div className="flex items-center gap-2">
                                        <Button type="button" size="sm" variant="outline" onClick={() => setMapPickerOpen(true)} data-testid="party-map-pick-btn">
                                            <MapPin className="h-4 w-4 mr-1.5" /> {form.latitude && form.longitude ? "Update Location" : "Pick on Map"}
                                        </Button>
                                        {form.latitude && form.longitude && (
                                            <>
                                                <Badge variant="secondary" className="font-mono text-[10px]" data-testid="party-coords-badge">
                                                    {form.latitude.toFixed(5)}, {form.longitude.toFixed(5)}
                                                </Badge>
                                                <Button type="button" size="sm" variant="ghost" onClick={() => openInGoogleMaps(form.latitude, form.longitude, form.name)} data-testid="party-open-gmaps-btn">
                                                    Open
                                                </Button>
                                                <Button type="button" size="sm" variant="ghost" className="text-destructive" onClick={() => setForm({ ...form, latitude: null, longitude: null })} data-testid="party-clear-coords-btn">
                                                    Clear
                                                </Button>
                                            </>
                                        )}
                                    </div>
                                </Field>
                                <Field label="Opening Balance (₹)">
                                    <Input data-testid="party-opening-balance-input" type="number" step="0.01" value={form.opening_balance} onChange={(e) => setForm({ ...form, opening_balance: e.target.value === "" ? 0 : parseFloat(e.target.value) || 0 })} />
                                </Field>
                                <Field label="Credit Limit (₹)">
                                    <Input data-testid="party-credit-limit-input" type="number" step="0.01" value={form.credit_limit} onChange={(e) => setForm({ ...form, credit_limit: e.target.value === "" ? 0 : parseFloat(e.target.value) || 0 })} />
                                </Field>
                            </div>
                            <DialogFooter className="gap-2 sm:gap-0">
                                <Button variant="outline" onClick={() => setOpen(false)} disabled={saving} data-testid="party-cancel-button">Cancel</Button>
                                <Button onClick={submit} disabled={saving} data-testid="party-save-button" className="bg-primary hover:bg-primary/90">
                                    {saving ? (<><Loader2 className="h-4 w-4 mr-2 animate-spin" /> Saving…</>) : (editing ? "Update" : "Save")}
                                </Button>
                            </DialogFooter>
                        </DialogContent>
                    </Dialog>
                    </div>
                )}
            </div>

            <Card>
                <CardContent className="p-0">
                    <div className="flex flex-wrap items-center justify-between gap-3 p-4 border-b border-border">
                        <Tabs value={tab} onValueChange={setTab}>
                            <TabsList>
                                <TabsTrigger value="all" data-testid="tab-all">All ({parties.length})</TabsTrigger>
                                <TabsTrigger value="customer" data-testid="tab-customer">Customers ({parties.filter(p => p.type === "customer").length})</TabsTrigger>
                                <TabsTrigger value="vendor" data-testid="tab-vendor">Vendors ({parties.filter(p => p.type === "vendor").length})</TabsTrigger>
                            </TabsList>
                        </Tabs>
                        <div className="relative">
                            <Search className="h-4 w-4 absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground" />
                            <Input placeholder="Search parties…" value={q} onChange={(e) => setQ(e.target.value)} className="pl-8 h-9 w-72" data-testid="parties-search" />
                        </div>
                    </div>
                    <div className="overflow-x-auto">
                        <table className="w-full dense-table text-sm" data-testid="parties-table">
                            <thead className="bg-muted/40">
                                <tr className="text-left text-[10px] tracking-wider uppercase text-muted-foreground">
                                    {selectMode && (
                                        <th className="w-10 pl-5">
                                            <Checkbox
                                                checked={filtered.length > 0 && filtered.every((r) => selected.has(r.id))}
                                                onCheckedChange={() => toggleAllVisible(filtered)}
                                                data-testid="parties-select-all"
                                                aria-label="Select all visible parties"
                                            />
                                        </th>
                                    )}
                                    <th className={selectMode ? "py-2.5" : "px-5 py-2.5"}>Name</th>
                                    <th>Type</th>
                                    <th>GSTIN</th>
                                    <th>Phone</th>
                                    <th>Address</th>
                                    <th className="text-right">Credit Limit</th>
                                    <th className="text-right">Outstanding</th>
                                    {isAdmin && !selectMode && <th className="text-right pr-5">Actions</th>}
                                </tr>
                            </thead>
                            <tbody>
                                {loading ? (
                                    <tr><td colSpan={selectMode ? 9 : 8} className="p-8 text-center text-muted-foreground">Loading…</td></tr>
                                ) : filtered.length === 0 ? (
                                    <tr><td colSpan={selectMode ? 9 : 8} className="p-12 text-center text-muted-foreground">No parties yet.</td></tr>
                                ) : filtered.map((p) => {
                                    const isChecked = selected.has(p.id);
                                    return (
                                    <tr
                                        key={p.id}
                                        className={`border-t border-border hover:bg-muted/20 ${isChecked ? "bg-primary/5" : ""}`}
                                        onClick={selectMode ? () => toggleOne(p.id) : undefined}
                                        style={selectMode ? { cursor: "pointer" } : undefined}
                                        title="Tip: drag this row onto the WhatsApp button to share"
                                        {...makeShareDraggable("party", {
                                            id: p.id,
                                            name: p.name,
                                            phone: p.phone,
                                            email: p.email,
                                            gstin: p.gstin,
                                            outstanding: p.balance || p.outstanding || 0,
                                            type: p.type,
                                        })}
                                    >
                                        {selectMode && (
                                            <td className="w-10 pl-5">
                                                <Checkbox
                                                    checked={isChecked}
                                                    onCheckedChange={() => toggleOne(p.id)}
                                                    onClick={(e) => e.stopPropagation()}
                                                    data-testid={`party-checkbox-${p.id}`}
                                                    aria-label={`Select ${p.name}`}
                                                />
                                            </td>
                                        )}
                                        <td className={`${selectMode ? "" : "pl-5"} py-1.5 font-medium`}>
                                            <div className="flex items-center gap-2.5">
                                                <div className="h-9 w-9 rounded-full overflow-hidden bg-muted flex items-center justify-center shrink-0 ring-1 ring-border">
                                                    {p.photo_url ? (
                                                        <img src={p.photo_url} alt="" className="h-full w-full object-cover" loading="lazy" />
                                                    ) : (
                                                        <span className="text-[11px] font-bold text-muted-foreground uppercase">{(p.name || "?").slice(0, 2)}</span>
                                                    )}
                                                </div>
                                                <span>{p.name}</span>
                                            </div>
                                        </td>
                                        <td><Badge variant={p.type === "customer" ? "default" : "secondary"} className="capitalize text-[10px]">{p.type}</Badge></td>
                                        <td className="font-mono text-xs">{p.gstin || "—"}</td>
                                        <td className="text-xs">{p.phone ? <span className="inline-flex items-center gap-1"><Phone className="h-3 w-3" />{p.phone}</span> : "—"}</td>
                                        <td className="text-xs text-muted-foreground truncate max-w-[180px]">{p.address ? <span className="inline-flex items-center gap-1"><MapPin className="h-3 w-3" />{p.address}</span> : "—"}</td>
                                        <td className="text-right num">{formatINR(p.credit_limit)}</td>
                                        <td className="text-right num font-medium text-amber-600 dark:text-amber-400">{formatINR(p.outstanding || 0)}</td>
                                        {isAdmin && !selectMode && (
                                            <td className="text-right pr-5 whitespace-nowrap">
                                                <Button size="icon" variant="ghost" className="h-8 w-8 text-fuchsia-600 hover:bg-fuchsia-50 dark:hover:bg-fuchsia-950/30" onClick={() => setLoyaltyParty(p)} data-testid={`loyalty-party-${p.id}`} title="Loyalty points"><Gift className="h-3.5 w-3.5" /></Button>
                                                <Button size="icon" variant="ghost" className="h-8 w-8" onClick={() => { setForm({ ...empty, ...p }); setEditing(p.id); setOpen(true); }} data-testid={`edit-party-${p.id}`}><Pencil className="h-3.5 w-3.5" /></Button>
                                                <Button size="icon" variant="ghost" className="h-8 w-8 text-destructive" onClick={() => remove(p)} data-testid={`delete-party-${p.id}`}><Trash2 className="h-3.5 w-3.5" /></Button>
                                            </td>
                                        )}
                                    </tr>
                                    );
                                })}
                            </tbody>
                        </table>
                    </div>
                </CardContent>
            </Card>

            <LoyaltyDialog
                open={!!loyaltyParty}
                onOpenChange={(v) => { if (!v) setLoyaltyParty(null); }}
                party={loyaltyParty}
                onUpdated={() => load()}
            />

            <MapPicker
                open={mapPickerOpen}
                initial={form.latitude && form.longitude ? { latitude: form.latitude, longitude: form.longitude, label: form.address } : null}
                onClose={() => setMapPickerOpen(false)}
                onSave={({ latitude, longitude, label }) => {
                    setForm((f) => ({ ...f, latitude, longitude, address: f.address || label || "" }));
                    toast.success("Location set");
                }}
                title={`Pick location for ${form.name || "this party"}`}
            />

            {/* Bulk delete confirmation */}
            <AlertDialog open={bulkConfirmOpen} onOpenChange={setBulkConfirmOpen}>
                <AlertDialogContent data-testid="parties-bulk-confirm-dialog">
                    <AlertDialogHeader>
                        <AlertDialogTitle>Delete {selected.size} selected {selected.size === 1 ? "party" : "parties"}?</AlertDialogTitle>
                        <AlertDialogDescription>
                            They&apos;ll be moved to the Recycle Bin and you have <strong>10 seconds</strong> to undo via the toast notification. After 30 days, they&apos;re permanently removed from Trash.
                        </AlertDialogDescription>
                    </AlertDialogHeader>
                    <AlertDialogFooter>
                        <AlertDialogCancel data-testid="parties-bulk-cancel-btn">Cancel</AlertDialogCancel>
                        <AlertDialogAction
                            onClick={performBulkDelete}
                            disabled={bulkDeleting}
                            className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
                            data-testid="parties-bulk-confirm-btn"
                        >
                            {bulkDeleting ? <Loader2 className="h-4 w-4 mr-1.5 animate-spin" /> : <Trash2 className="h-4 w-4 mr-1.5" />}
                            Move to Trash
                        </AlertDialogAction>
                    </AlertDialogFooter>
                </AlertDialogContent>
            </AlertDialog>
        </div>
    );
}

function Field({ label, full, children, error }) {
    return (
        <div className={`space-y-1 ${full ? "sm:col-span-2" : ""}`}>
            <Label className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">{label}</Label>
            {children}
            {error && (
                <div className="text-[11px] text-destructive flex items-center gap-1" data-testid={`field-error-${(label || "").toLowerCase().replace(/[^a-z]+/g, "-").replace(/-$/, "")}`}>
                    <AlertTriangle className="h-3 w-3" /> {error}
                </div>
            )}
        </div>
    );
}


function ImportPartiesButton({ activeId, onDone }) {
    const [open, setOpen] = useState(false);
    const [file, setFile] = useState(null);
    const [mode, setMode] = useState("skip");
    const [busy, setBusy] = useState(false);
    const [result, setResult] = useState(null);

    const downloadTemplate = async () => {
        try {
            const res = await api.get("/parties/import/template", { responseType: "blob" });
            const url = URL.createObjectURL(res.data);
            const a = document.createElement("a");
            a.href = url; a.download = "RGERegalgoa-Parties-Import-Template.xlsx"; a.click();
            URL.revokeObjectURL(url);
            toast.success("Template downloaded — fill rows and re-upload");
        } catch (e) {
            toast.error("Could not download template");
        }
    };

    const upload = async () => {
        if (!file) { toast.error("Choose a .xlsx file first"); return; }
        const fd = new FormData();
        fd.append("file", file);
        setBusy(true); setResult(null);
        try {
            const res = await api.post("/parties/import", fd, {
                params: { company_id: activeId, mode },
                headers: { "Content-Type": "multipart/form-data" },
            });
            setResult(res.data);
            const { created, updated, skipped, errors } = res.data;
            toast.success(`✓ ${created} created · ${updated} updated · ${skipped} skipped${errors?.length ? ` · ${errors.length} errors` : ""}`);
            onDone?.();
        } catch (e) {
            toast.error(formatApiError(e.response?.data?.detail) || "Import failed");
        } finally { setBusy(false); }
    };

    return (
        <Dialog open={open} onOpenChange={(v) => { setOpen(v); if (!v) { setFile(null); setResult(null); } }}>
            <DialogTrigger asChild>
                <Button variant="outline" data-testid="import-parties-btn"><Upload className="h-4 w-4 mr-1.5" /> Import</Button>
            </DialogTrigger>
            <DialogContent className="max-w-xl">
                <DialogHeader><DialogTitle>Bulk Import Parties</DialogTitle></DialogHeader>
                <div className="space-y-4">
                    {/* Step 1: Template */}
                    <div className="rounded-lg border border-border bg-muted/20 p-4">
                        <div className="flex items-start justify-between gap-3 flex-wrap">
                            <div>
                                <div className="font-semibold text-sm flex items-center gap-1.5"><span className="h-5 w-5 rounded-full bg-primary text-primary-foreground inline-flex items-center justify-center text-[10px] font-bold">1</span> Download template</div>
                                <p className="text-xs text-muted-foreground mt-1">Fill the rows in Excel / Google Sheets. Keep the header row unchanged.</p>
                            </div>
                            <Button size="sm" variant="outline" onClick={downloadTemplate} data-testid="import-template-btn"><FileDown className="h-3.5 w-3.5 mr-1.5" /> Template.xlsx</Button>
                        </div>
                        <ul className="mt-3 text-[11px] text-muted-foreground space-y-0.5 grid grid-cols-2 gap-x-3">
                            <li>• <b>Name*</b> required</li>
                            <li>• Party: CUSTOMER · SUPPLIER · EXPENSE · General</li>
                            <li>• Opening Date: dd/MM/yyyy</li>
                            <li>• Opening Balance: -ve = you owe them</li>
                        </ul>
                    </div>

                    {/* Step 2: Upload */}
                    <div className="rounded-lg border border-border p-4 space-y-3">
                        <div className="font-semibold text-sm flex items-center gap-1.5"><span className="h-5 w-5 rounded-full bg-primary text-primary-foreground inline-flex items-center justify-center text-[10px] font-bold">2</span> Upload filled file</div>
                        <label className="block">
                            <input
                                type="file"
                                accept=".xlsx,.xls,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
                                className="block w-full text-sm file:mr-3 file:py-1.5 file:px-3 file:rounded-md file:border-0 file:bg-primary file:text-primary-foreground file:cursor-pointer hover:file:bg-primary/90"
                                onChange={(e) => setFile(e.target.files?.[0] || null)}
                                data-testid="import-file-input"
                            />
                        </label>
                        <div className="flex items-center gap-3 text-xs">
                            <span className="label-cap">If party exists:</span>
                            <label className="inline-flex items-center gap-1.5"><input type="radio" name="mode" checked={mode === "skip"} onChange={() => setMode("skip")} data-testid="import-mode-skip" /> Skip duplicates</label>
                            <label className="inline-flex items-center gap-1.5"><input type="radio" name="mode" checked={mode === "update"} onChange={() => setMode("update")} data-testid="import-mode-update" /> Update existing</label>
                        </div>
                    </div>

                    {/* Result */}
                    {result && (
                        <div className="rounded-lg border border-emerald-500/30 bg-emerald-50/30 dark:bg-emerald-950/20 p-4 space-y-2" data-testid="import-result">
                            <div className="flex items-center gap-2 font-semibold text-sm text-emerald-700 dark:text-emerald-400">
                                <CheckCircle2 className="h-4 w-4" /> Import completed
                            </div>
                            <div className="grid grid-cols-3 gap-2 text-center">
                                <div><div className="num text-2xl font-bold text-emerald-700">{result.created}</div><div className="text-[10px] uppercase text-muted-foreground">Created</div></div>
                                <div><div className="num text-2xl font-bold text-blue-700">{result.updated}</div><div className="text-[10px] uppercase text-muted-foreground">Updated</div></div>
                                <div><div className="num text-2xl font-bold text-muted-foreground">{result.skipped}</div><div className="text-[10px] uppercase text-muted-foreground">Skipped</div></div>
                            </div>
                            {result.errors?.length > 0 && (
                                <div className="mt-2 max-h-40 overflow-auto rounded border border-amber-500/40 bg-amber-50/50 dark:bg-amber-950/20 p-2 text-xs space-y-1">
                                    <div className="flex items-center gap-1 font-semibold text-amber-700"><AlertTriangle className="h-3.5 w-3.5" /> {result.errors.length} warning(s)</div>
                                    {result.errors.map((e, i) => <div key={`err-${i}-${String(e).slice(0, 40)}`} className="font-mono text-[10px]">• {e}</div>)}
                                </div>
                            )}
                        </div>
                    )}
                </div>
                <DialogFooter>
                    <Button variant="outline" onClick={() => setOpen(false)}>Close</Button>
                    <Button onClick={upload} disabled={!file || busy} className="bg-primary hover:bg-primary/90" data-testid="import-upload-btn">
                        {busy ? "Importing…" : <><Upload className="h-4 w-4 mr-1.5" /> Import parties</>}
                    </Button>
                </DialogFooter>
            </DialogContent>
        </Dialog>
    );
}
