import * as DM from '@radix-ui/react-dropdown-menu';
import { ChevronDown, Copy, GitCompare, History, Loader2, RotateCcw } from 'lucide-react';
import { useCallback, useEffect, useState } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardBody, CardHeader } from '@/components/ui/card';
import { Dialog } from '@/components/ui/dialog';
import { StatusBadge } from '@/components/ui/badge';
import { date, money } from '@/lib/format';
import { http, HttpError } from '@/lib/http';
import { cn } from '@/lib/utils';

export type Workflow = {
    targets: string[]; targets_by_status: Record<string, string[]>; labels: Record<string, string>; sent_states: string[]; locked: string[];
    current_version: number; follow_up: 'follow_up_1' | 'follow_up_2' | null; can_unaccept?: boolean;
};

const itemClass = 'flex cursor-pointer items-center gap-2.5 rounded-[5px] px-2.5 py-2 text-sm text-ink outline-none data-[highlighted]:bg-accent-soft data-[highlighted]:text-link';

const HINTS: Record<string, string> = {
    waiting: 'Waiting on the client (e.g. they asked for time)',
    withdrawn: 'Take the quote back; the client can no longer accept it',
    completed: 'The work is done',
    invoiced: 'The work has been invoiced',
    dismissed: 'Archive it (hidden from pipeline and follow-ups)',
    sent: 'Reopen as Sent',
    draft: 'Back to draft',
    accepted: 'Undo "completed"',
};

/** "Status ▾": the manual moves the state machine allows from the current status. */
export function StatusMenu({ quoteId, companyId, status, workflow, onChanged }: {
    quoteId: number; companyId: number; status: string; workflow: Workflow; onChanged: (status: string) => void;
}) {
    const [busy, setBusy] = useState(false);
    const [error, setError] = useState<string | null>(null);
    const targets = workflow.targets;
    if (targets.length === 0) return null;
    const move = async (to: string) => {
        setBusy(true);
        setError(null);
        try {
            const res = await http<{ status: string }>('POST', `/quotes/${quoteId}/status`, { status: to }, { 'X-Company': String(companyId) });
            onChanged(res.status);
        } catch (e) {
            setError(e instanceof HttpError ? (e.body?.message ?? 'Couldn’t change the status.') : 'Couldn’t change the status.');
        } finally { setBusy(false); }
    };

    return (
        <DM.Root>
            <DM.Trigger asChild>
                <Button type="button" variant="secondary" disabled={busy} data-testid="status-menu" title={error ?? undefined}>
                    {busy ? <Loader2 className="h-4 w-4 animate-spin" /> : null} Status <ChevronDown className="h-4 w-4" />
                </Button>
            </DM.Trigger>
            <DM.Portal>
                <DM.Content align="end" sideOffset={6} className="motion-pop z-50 min-w-64 rounded-[var(--radius-card)] border border-line bg-surface p-1.5 shadow-pop">
                    <div className="px-2.5 pb-1.5 pt-1 text-xs text-ink-muted">Now <StatusBadge status={status} /></div>
                    {targets.map((t) => (
                        <DM.Item key={t} className={itemClass} onSelect={() => void move(t)} data-testid={`status-${t}`}>
                            <div><div className="font-medium">Mark as {workflow.labels[t] ?? t}</div><div className="text-xs text-ink-muted">{HINTS[t]}</div></div>
                        </DM.Item>
                    ))}
                    {error && <p role="alert" className="px-2.5 py-1.5 text-xs text-danger">{error}</p>}
                </DM.Content>
            </DM.Portal>
        </DM.Root>
    );
}

type VersionRow = { id: number; version_no: number; reason: string; status: string; total_cents: number; by: string | null; at: string };
type Summary = { type: string; heading: string; code: string | null; description: string; qty: string | null; unit_price_cents: number | null; discount_bp: number | null; flags: string[] };
type Diff = {
    fields: { key: string; label: string; before: string; after: string }[];
    lines: { status: 'same' | 'changed' | 'added' | 'removed'; before: Summary | null; after: Summary | null; changed: string[] }[];
    totals: { before: { subtotal_cents: number; tax_cents: number; total_cents: number }; after: { subtotal_cents: number; tax_cents: number; total_cents: number } };
};

/** Versions: kept automatically on edit-after-send / edit-after-accept / restore. Compare, restore & replace, or copy to a new quote. */
export function VersionsCard({ quoteId, companyId, refreshKey, onRestored }: { quoteId: number; companyId: number; refreshKey: unknown; onRestored: (message: string) => void }) {
    const [data, setData] = useState<{ versions: VersionRow[]; current: { version_no: number; total_cents: number; status: string }; currency: string; keep_months: number } | null>(null);
    const [open, setOpen] = useState<VersionRow | null>(null);
    const load = useCallback(() => {
        http<typeof data>('GET', `/quotes/${quoteId}/versions`, undefined, { 'X-Company': String(companyId) }).then(setData).catch(() => {});
    }, [quoteId, companyId]);
    useEffect(() => { load(); }, [load, refreshKey]);
    if (!data) return null;
    const cur = data.currency;

    return (
        <Card data-testid="versions">
            <CardHeader title="Versions" description={`Kept for ${data.keep_months} months. A version is saved when you edit a sent or accepted quote.`} />
            <CardBody className="space-y-2">
                <div className="flex items-center justify-between rounded-md bg-page px-3 py-2 text-sm">
                    <span><b className="font-medium">Version {data.current.version_no}</b> · current</span>
                    <span className="tabular-nums">{money(data.current.total_cents, cur)}</span>
                </div>
                {data.versions.length === 0 && <p className="text-sm text-ink-muted">No earlier versions yet.</p>}
                <ol className="space-y-1.5">
                    {data.versions.map((v) => (
                        <li key={v.id}>
                            <button type="button" onClick={() => setOpen(v)} data-testid={`version-${v.version_no}`}
                                className="flex w-full items-start justify-between gap-3 rounded-md border border-line px-3 py-2 text-left text-sm hover:border-accent hover:bg-accent-soft/40">
                                <span className="min-w-0">
                                    <span className="font-medium">Version {v.version_no}</span> <StatusBadge status={v.status} />
                                    <span className="block truncate text-xs text-ink-muted">{v.reason}{v.by ? ` · ${v.by}` : ''} · {date(v.at)}</span>
                                </span>
                                <span className="shrink-0 tabular-nums">{money(v.total_cents, cur)}</span>
                            </button>
                        </li>
                    ))}
                </ol>
            </CardBody>
            {open && <CompareDialog quoteId={quoteId} companyId={companyId} version={open} currency={cur} currentNo={data.current.version_no}
                onClose={() => setOpen(null)} onRestored={(m) => { setOpen(null); onRestored(m); }} />}
        </Card>
    );
}

function CompareDialog({ quoteId, companyId, version, currency, currentNo, onClose, onRestored }: {
    quoteId: number; companyId: number; version: VersionRow; currency: string; currentNo: number; onClose: () => void; onRestored: (m: string) => void;
}) {
    const [diff, setDiff] = useState<Diff | null>(null);
    const [busy, setBusy] = useState<'restore' | 'copy' | null>(null);
    const [confirm, setConfirm] = useState(false);
    const [error, setError] = useState<string | null>(null);
    const [onlyChanges, setOnlyChanges] = useState(true);
    const h = { 'X-Company': String(companyId) };
    useEffect(() => {
        http<{ diff: Diff }>('GET', `/quotes/${quoteId}/versions/${version.id}`, undefined, h).then((r) => setDiff(r.diff)).catch(() => setError('Couldn’t load this version.'));
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [quoteId, version.id]);

    const restore = async () => {
        setBusy('restore');
        try {
            const r = await http<{ message: string }>('POST', `/quotes/${quoteId}/versions/${version.id}/restore`, {}, h);
            onRestored(r.message);
        } catch (e) { setError(e instanceof HttpError ? (e.body?.message ?? 'Couldn’t restore.') : 'Couldn’t restore.'); setBusy(null); }
    };
    const copy = async () => {
        setBusy('copy');
        try {
            const r = await http<{ href: string }>('POST', `/quotes/${quoteId}/versions/${version.id}/copy`, {}, h);
            window.location.href = r.href;
        } catch (e) { setError(e instanceof HttpError ? (e.body?.message ?? 'Couldn’t copy.') : 'Couldn’t copy.'); setBusy(null); }
    };
    const rows = diff ? (onlyChanges ? diff.lines.filter((l) => l.status !== 'same') : diff.lines) : [];
    const changes = diff ? diff.fields.length + diff.lines.filter((l) => l.status !== 'same').length : 0;

    return (
        <Dialog open onOpenChange={(o) => !o && onClose()} size="xl" title={`Version ${version.version_no} compared with the current quote`}
            description={`${version.reason} · ${date(version.at)}. Changes are highlighted: green was added in the current quote, red was removed.`}>
            {error && <p role="alert" className="mb-3 rounded-md bg-red-50 px-3 py-2 text-sm text-red-800">{error}</p>}
            {!diff ? <p className="flex items-center gap-2 text-sm text-ink-muted"><Loader2 className="h-4 w-4 animate-spin" /> Loading…</p> : (
                <div className="space-y-4" data-testid="version-diff">
                    <div className="grid grid-cols-2 gap-3 text-xs font-semibold uppercase tracking-wide text-ink-muted">
                        <div className="flex items-center gap-2"><History className="h-3.5 w-3.5" /> Version {version.version_no}</div>
                        <div className="flex items-center gap-2"><GitCompare className="h-3.5 w-3.5" /> Current (version {currentNo})</div>
                    </div>
                    {changes === 0 && <p className="text-sm text-ink-muted">No differences.</p>}
                    {diff.fields.length > 0 && (
                        <div className="space-y-1.5">
                            {diff.fields.map((f) => (
                                <div key={f.key} className="grid grid-cols-2 gap-3 text-sm">
                                    <div className="rounded-md bg-red-50 px-3 py-2"><div className="text-xs text-ink-muted">{f.label}</div><div className="break-words text-red-900">{f.before}</div></div>
                                    <div className="rounded-md bg-green-50 px-3 py-2"><div className="text-xs text-ink-muted">{f.label}</div><div className="break-words text-green-900">{f.after}</div></div>
                                </div>
                            ))}
                        </div>
                    )}
                    <div>
                        <div className="mb-1.5 flex items-center justify-between">
                            <h3 className="text-sm font-semibold">Items</h3>
                            <label className="inline-flex items-center gap-2 text-xs"><input type="checkbox" checked={onlyChanges} onChange={(e) => setOnlyChanges(e.target.checked)} /> Changes only</label>
                        </div>
                        <div className="space-y-1.5">
                            {rows.length === 0 && <p className="text-sm text-ink-muted">No item changes.</p>}
                            {rows.map((l, i) => (
                                <div key={i} className="grid grid-cols-2 gap-3 text-sm" data-testid={`diff-${l.status}`}>
                                    <LineCell s={l.before} tone={l.status === 'removed' ? 'removed' : l.status === 'changed' ? 'changed' : 'same'} changed={l.changed} currency={currency} empty={l.status === 'added'} />
                                    <LineCell s={l.after} tone={l.status === 'added' ? 'added' : l.status === 'changed' ? 'changed' : 'same'} changed={l.changed} currency={currency} empty={l.status === 'removed'} />
                                </div>
                            ))}
                        </div>
                    </div>
                    <div className="grid grid-cols-2 gap-3 border-t border-line pt-3 text-sm">
                        {(['before', 'after'] as const).map((k) => (
                            <div key={k} className="space-y-0.5 tabular-nums">
                                <div className="flex justify-between"><span className="text-ink-muted">Subtotal</span>{money(diff.totals[k].subtotal_cents, currency)}</div>
                                <div className="flex justify-between"><span className="text-ink-muted">Tax</span>{money(diff.totals[k].tax_cents, currency)}</div>
                                <div className={cn('flex justify-between font-semibold', diff.totals.before.total_cents !== diff.totals.after.total_cents && (k === 'before' ? 'text-red-800' : 'text-green-800'))}>
                                    <span>Total</span><span data-testid={`diff-total-${k}`}>{money(diff.totals[k].total_cents, currency)}</span></div>
                            </div>
                        ))}
                    </div>
                    {confirm ? (
                        <div role="alert" className="rounded-md border border-amber-300 bg-amber-50 px-4 py-3 text-sm text-amber-900">
                            <p>Replace the current quote with version {version.version_no}? The current content is kept as a new version first{version.status !== 'draft' ? ', and a sent quote moves to Editing until you resend it' : ''}.</p>
                            <div className="mt-2 flex justify-end gap-2">
                                <Button type="button" variant="secondary" size="sm" onClick={() => setConfirm(false)}>Cancel</Button>
                                <Button type="button" size="sm" onClick={restore} disabled={!!busy} data-testid="restore-confirm">{busy === 'restore' && <Loader2 className="h-4 w-4 animate-spin" />} Restore & replace</Button>
                            </div>
                        </div>
                    ) : (
                        <div className="flex flex-wrap justify-end gap-2 border-t border-line pt-4">
                            <Button type="button" variant="secondary" onClick={copy} disabled={!!busy} data-testid="version-copy">{busy === 'copy' ? <Loader2 className="h-4 w-4 animate-spin" /> : <Copy className="h-4 w-4" />} Copy to a new quote</Button>
                            <Button type="button" onClick={() => setConfirm(true)} disabled={!!busy} data-testid="version-restore"><RotateCcw className="h-4 w-4" /> Restore this version</Button>
                        </div>
                    )}
                </div>
            )}
        </Dialog>
    );
}

const FIELD_LABELS: Record<string, string> = { unit_price_cents: 'price', qty: 'qty', discount_bp: 'discount', heading: 'heading', description: 'description', selected: 'selection', optional: 'optional', tax_rate_bp: 'tax', code: 'code' };

function LineCell({ s, tone, changed, currency, empty }: { s: Summary | null; tone: 'same' | 'changed' | 'added' | 'removed'; changed: string[]; currency: string; empty: boolean }) {
    if (empty || !s) return <div className="rounded-md border border-dashed border-line px-3 py-2 text-xs text-ink-muted">{tone === 'removed' ? '' : 'Not in this version'}</div>;
    const hl = (f: string) => tone === 'changed' && changed.includes(f) && 'rounded bg-amber-200/70 px-1';
    return (
        <div className={cn('rounded-md px-3 py-2', tone === 'added' && 'bg-green-50 text-green-900', tone === 'removed' && 'bg-red-50 text-red-900 line-through decoration-red-300', tone === 'changed' && 'bg-amber-50', tone === 'same' && 'bg-page')}>
            <div className="flex items-start justify-between gap-2">
                <span className={cn('font-medium', hl('heading'))}>{s.code ? <span className="mr-1 text-xs text-ink-muted">{s.code}</span> : null}{s.heading}</span>
                {s.type === 'price' && (
                    <span className="shrink-0 text-right tabular-nums">
                        <span className={cn(hl('qty'))}>{s.qty}</span> × <span className={cn(hl('unit_price_cents'))}>{money(s.unit_price_cents ?? 0, currency)}</span>
                        {s.discount_bp ? <span className={cn('block text-xs', hl('discount_bp'))}>−{s.discount_bp / 100}%</span> : null}
                    </span>
                )}
            </div>
            {s.description && <p className={cn('mt-0.5 line-clamp-2 text-xs text-ink-muted', hl('description'))}>{s.description}</p>}
            {s.flags.length > 0 && <p className={cn('mt-0.5 text-xs text-ink-muted', hl('selected'))}>{s.flags.join(' · ')}</p>}
            {tone === 'changed' && changed.length > 0 && <p className="mt-1 text-[11px] text-amber-800">Changed: {changed.map((c) => FIELD_LABELS[c] ?? c.replace(/_/g, ' ')).join(', ')}</p>}
        </div>
    );
}
