import { Check, Clock, Eye, Loader2, Paperclip, Save, Send, X } from 'lucide-react';
import { dateTime } from '@/lib/format';
import { useEffect, useMemo, useState } from 'react';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Dialog } from '@/components/ui/dialog';
import { Input, Select } from '@/components/ui/input';
import { EmailEditor } from '@/components/EmailEditor';
import { Field, FieldError } from '@/components/ui/label';
import { http, HttpError, httpForm } from '@/lib/http';

export type Compose = {
    type: string;
    templates: Record<string, { label: string; subject: string; body: string }>;
    to: Person[];
    from: { name: string; email: string; verified: boolean };
    reply_to: string;
    placeholders: Record<string, string>;
    test_mode: boolean;
    timezone: string;
    /** A message saved from this dialog without sending (cleared once the quote is sent). */
    draft?: Draft | null;
};
type Draft = { type: string; subject: string; body: string; to: Person[]; cc: string; bcc: string; saved_at: string; saved_by: string };
type Person = { contact_id?: number | null; name?: string | null; email: string };
type Contact = { id: number; name: string; email: string | null; company: string | null };

const emailOk = (e: string) => /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(e.trim());
const list = (s: string) => s.split(/[,;\s]+/).map((x) => x.trim()).filter(Boolean);

/** Send / schedule the quote: each recipient gets their own tracked link. */
export function SendDialog({ open, onOpenChange, quoteId, companyId, compose, contacts, onSent }: {
    open: boolean; onOpenChange: (o: boolean) => void; quoteId: number; companyId: number; compose: Compose | null; contacts: Contact[];
    onSent: (message: string) => void;
}) {
    const [type, setType] = useState('new_quote');
    const [to, setTo] = useState<Person[]>([]);
    const [adding, setAdding] = useState('');
    const [cc, setCc] = useState('');
    const [bcc, setBcc] = useState('');
    const [showCc, setShowCc] = useState(false);
    const [subject, setSubject] = useState('');
    const [body, setBody] = useState('');
    const [files, setFiles] = useState<File[]>([]);
    const [schedule, setSchedule] = useState(false);
    const [at, setAt] = useState('');
    const [copy, setCopy] = useState(false);
    const [errors, setErrors] = useState<Record<string, string>>({});
    const [busy, setBusy] = useState(false);
    const [preview, setPreview] = useState<{ subject: string; html: string; to: string } | null>(null);
    const [previewBusy, setPreviewBusy] = useState(false);
    const showPreview = async () => {
        setPreviewBusy(true); setErrors({});
        const first = to[0];
        try {
            const res = await http<{ subject: string; html: string }>('POST', `/quotes/${quoteId}/email-preview`, { subject, body, name: first?.name ?? null }, { 'X-Company': String(companyId) });
            setPreview({ ...res, to: first ? (first.name ? `${first.name} <${first.email}>` : first.email) : '' });
        } catch (err) {
            setErrors({ form: err instanceof HttpError ? err.message : 'Couldn’t build the preview.' });
        } finally { setPreviewBusy(false); }
    };

    useEffect(() => {
        if (!compose || !open) return;
        const d = compose.draft;
        setType(d?.type ?? compose.type);
        setTo(d?.to?.length ? d.to : compose.to);
        setSubject(d ? d.subject : compose.templates[compose.type]?.subject ?? '');
        setBody(d ? d.body : compose.templates[compose.type]?.body ?? '');
        setFiles([]); setErrors({}); setSchedule(false); setAt(''); setCc(d?.cc ?? ''); setBcc(d?.bcc ?? '');
        if (d && (d.cc || d.bcc)) setShowCc(true);
        setDraft(d ?? null);
    }, [compose, open]);

    // ---- Save without sending ----
    const [draft, setDraft] = useState<Draft | null>(null);
    const [draftBusy, setDraftBusy] = useState(false);
    const snapshot = JSON.stringify([type, subject, body, to.map((p) => p.email), cc, bcc]);
    const draftSnapshot = draft ? JSON.stringify([draft.type, draft.subject, draft.body, (draft.to ?? []).map((p) => p.email), draft.cc, draft.bcc]) : null;
    const draftCurrent = draftSnapshot === snapshot;
    const saveDraft = async () => {
        setDraftBusy(true); setErrors({});
        try {
            const res = await http<{ draft: Draft }>('PUT', `/quotes/${quoteId}/email-draft`, {
                type, subject, body, cc, bcc, to: to.map((p) => ({ email: p.email, name: p.name ?? null, contact_id: p.contact_id ?? null })),
            }, { 'X-Company': String(companyId) });
            setDraft({ ...res.draft, body }); // keep the editor's own HTML so it doesn't count as a change
        } catch (err) {
            setErrors({ form: err instanceof HttpError ? err.message : 'Couldn’t save the message.' });
        } finally { setDraftBusy(false); }
    };
    const discardDraft = async () => {
        if (!compose || !window.confirm('Discard the saved message and start again from the template?')) return;
        try { await http('DELETE', `/quotes/${quoteId}/email-draft`, undefined, { 'X-Company': String(companyId) }); } catch { /* ignore */ }
        setDraft(null);
        setType(compose.type); setTo(compose.to); setCc(''); setBcc('');
        setSubject(compose.templates[compose.type]?.subject ?? ''); setBody(compose.templates[compose.type]?.body ?? '');
    };

    const pickType = (t: string) => {
        setType(t);
        setSubject(compose?.templates[t]?.subject ?? '');
        setBody(compose?.templates[t]?.body ?? '');
    };
    const others = useMemo(() => contacts.filter((c) => c.email && !to.some((p) => p.email.toLowerCase() === c.email!.toLowerCase())), [contacts, to]);
    const addPerson = (v: string) => {
        const c = contacts.find((x) => String(x.id) === v);
        if (c?.email) setTo((t) => [...t, { contact_id: c.id, name: c.name, email: c.email! }]);
        else if (emailOk(v) && !to.some((p) => p.email.toLowerCase() === v.toLowerCase())) setTo((t) => [...t, { email: v.trim() }]);
        setAdding('');
    };
    const tooBig = files.find((f) => f.size > 10 * 1024 * 1024);
    const badCc = [...list(cc), ...list(bcc)].find((e) => !emailOk(e));

    const submit = async (e: React.FormEvent) => {
        e.preventDefault();
        if (adding.trim()) addPerson(adding.trim());
        const people = adding.trim() && emailOk(adding) ? [...to, { email: adding.trim() }] : to;
        if (!people.length) { setErrors({ to: 'Add at least one recipient.' }); return; }
        setBusy(true);
        try {
            const res = await httpForm<{ message: string }>(`/quotes/${quoteId}/send`, {
                type, to: people.map((p) => ({ email: p.email, name: p.name ?? '', contact_id: p.contact_id ?? '' })), cc: list(cc), bcc: list(bcc),
                subject, body, files, scheduled_at: schedule ? at : '', copy_to_owner: copy,
            }, { 'X-Company': String(companyId) });
            onOpenChange(false);
            onSent(res.message);
        } catch (err) {
            if (err instanceof HttpError && err.status === 422) {
                const out: Record<string, string> = {};
                for (const [k, v] of Object.entries((err.body?.errors ?? {}) as Record<string, string[]>)) out[k.replace(/\.\d+.*$/, '')] = v[0];
                if (!Object.keys(out).length) out.form = err.body?.message ?? 'Could not send.';
                setErrors(out);
            } else setErrors({ form: 'Could not send. Please try again.' });
        } finally {
            setBusy(false);
        }
    };

    if (!compose) return null;
    return (
        <Dialog open={open} onOpenChange={onOpenChange} title="Send quote" size="xl"
            description="Each person gets their own personal link, so you can see who opened and viewed it.">
            <form onSubmit={submit} className="space-y-4" noValidate data-testid="send-dialog">
                {compose.test_mode && (
                    <p className="rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-sm text-amber-900">
                        Test mode: no email provider is connected yet, so emails are written to the server log instead of being delivered. Client links still work.
                    </p>
                )}
                {errors.form && <p className="rounded-md bg-red-50 px-3 py-2 text-sm text-red-800" role="alert">{errors.form}</p>}
                {draft && (
                    <div className="flex flex-wrap items-center gap-2 rounded-md border border-line bg-stone-50 px-3 py-2 text-sm" data-testid="email-draft-status" role="status">
                        <Save className="h-4 w-4 text-ink-muted" aria-hidden />
                        <span className="min-w-0 flex-1">
                            {draftCurrent ? 'Saved message' : 'Unsaved changes to the saved message'} · {dateTime(draft.saved_at)}{draft.saved_by ? ` by ${draft.saved_by}` : ''}. It stays here until you send it.
                        </span>
                        <button type="button" className="text-xs text-link underline" onClick={() => void discardDraft()} data-testid="discard-email-draft">Discard and use the template</button>
                    </div>
                )}

                <div className="grid gap-4 sm:grid-cols-[1fr_220px]">
                    <div>
                        <span className="mb-1 block text-sm font-medium">To</span>
                        <div className="flex min-h-[38px] flex-wrap items-center gap-1.5 rounded-[5px] border border-line-strong px-2 py-1.5" data-testid="send-to">
                            {to.map((p) => (
                                <span key={p.email} className="inline-flex items-center gap-1 rounded-full bg-accent-soft px-2.5 py-0.5 text-[13px]">
                                    {p.name ? <><b className="font-medium">{p.name}</b> <span className="text-ink-muted">{p.email}</span></> : p.email}
                                    <button type="button" aria-label={`Remove ${p.email}`} onClick={() => setTo((t) => t.filter((x) => x !== p))} className="rounded-full p-0.5 hover:bg-black/10"><X className="h-3 w-3" /></button>
                                </span>
                            ))}
                            <input aria-label="Add recipient email" list="send-contacts" value={adding} placeholder={to.length ? 'Add another…' : 'Name or email'}
                                onChange={(e) => { const v = e.target.value; const c = contacts.find((x) => `${x.name} <${x.email}>` === v); if (c) addPerson(String(c.id)); else setAdding(v); }}
                                onKeyDown={(e) => { if ((e.key === 'Enter' || e.key === ',') && adding.trim()) { e.preventDefault(); addPerson(adding.trim()); } }}
                                className="min-w-40 flex-1 bg-transparent px-1 text-sm outline-none" />
                            <datalist id="send-contacts">{others.map((c) => <option key={c.id} value={`${c.name} <${c.email}>`} />)}</datalist>
                        </div>
                        <FieldError message={errors.to} />
                        {!showCc && <button type="button" className="mt-1 text-xs text-link underline" onClick={() => setShowCc(true)}>Add CC / BCC</button>}
                    </div>
                    <Field label="Email" htmlFor="send-type">
                        <Select id="send-type" value={type} onChange={(e) => pickType(e.target.value)}>
                            {Object.entries(compose.templates).map(([k, t]) => <option key={k} value={k}>{t.label}</option>)}
                        </Select>
                    </Field>
                </div>
                {showCc && (
                    <div className="grid gap-4 sm:grid-cols-2">
                        <Field label="CC" htmlFor="send-cc" hint="Separate with commas. Each gets their own tracked link."><Input id="send-cc" value={cc} onChange={(e) => setCc(e.target.value)} /></Field>
                        <Field label="BCC" htmlFor="send-bcc"><Input id="send-bcc" value={bcc} onChange={(e) => setBcc(e.target.value)} /></Field>
                    </div>
                )}
                {(badCc || errors.cc || errors.bcc) && <FieldError message={badCc ? `${badCc} isn't a valid email address.` : errors.cc ?? errors.bcc} />}

                <p className="text-xs text-ink-muted">
                    From <b className="font-medium text-ink">{compose.from.name}</b> &lt;{compose.from.email}&gt; · replies go to {compose.reply_to}
                    {!compose.from.verified && <> · <a href="/settings/domains" className="text-link underline">verify your sending domain</a> for best delivery</>}
                </p>

                <Field label="Subject" htmlFor="send-subject" error={errors.subject}><Input id="send-subject" value={subject} onChange={(e) => setSubject(e.target.value)} /></Field>
                <Field label="Message" htmlFor="send-body" error={errors.body} hint="Format the text and add images with the toolbar. “Quote button” adds the quote card with its personal “View quote” link.">
                    <EmailEditor id="send-body" label="Message" value={body} onChange={setBody} placeholders={compose.placeholders} minHeight="min-h-48" />
                </Field>
                <details className="text-xs text-ink-muted">
                    <summary className="cursor-pointer">Placeholders</summary>
                    <div className="mt-1 grid gap-x-4 gap-y-0.5 sm:grid-cols-2">
                        {Object.entries(compose.placeholders).map(([k, v]) => (
                            <button key={k} type="button" className="text-left hover:text-link" onClick={() => setBody((b) => `${b}${k}`)}><code>{k}</code> {v}</button>
                        ))}
                    </div>
                </details>

                <div className="flex flex-wrap items-center gap-3 text-sm">
                    <label className="inline-flex cursor-pointer items-center gap-1.5 rounded-[5px] border border-line-strong px-3 py-1.5 font-medium hover:border-accent">
                        <Paperclip className="h-4 w-4" /> Attach files
                        <input type="file" multiple className="sr-only" onChange={(e) => setFiles((f) => [...f, ...(e.target.files ?? [])].slice(0, 5))} />
                    </label>
                    {files.map((f) => (
                        <span key={f.name} className={`inline-flex items-center gap-1 rounded-full bg-stone-100 px-2.5 py-0.5 text-xs ${f.size > 10485760 ? 'text-red-700' : ''}`}>
                            {f.name} <button type="button" aria-label={`Remove ${f.name}`} onClick={() => setFiles((x) => x.filter((y) => y !== f))}><X className="h-3 w-3" /></button>
                        </span>
                    ))}
                    <span className="text-xs text-ink-muted">Up to 5 files, 10 MB each.</span>
                </div>
                <FieldError message={tooBig ? `${tooBig.name} is larger than 10 MB.` : errors.files} />

                <div className="flex flex-wrap items-center gap-x-6 gap-y-3 border-t border-line pt-4">
                    <Checkbox label="Send me a copy" checked={copy} onChange={(e) => setCopy(e.target.checked)} />
                    <Checkbox label="Schedule for later" checked={schedule} onChange={(e) => setSchedule(e.target.checked)} data-testid="send-schedule" />
                    {schedule && (
                        <div className="flex items-center gap-2">
                            <Input type="datetime-local" aria-label="Send at" value={at} onChange={(e) => setAt(e.target.value)} className="w-56" />
                            <span className="text-xs text-ink-muted">{compose.timezone.replace('_', ' ')}</span>
                        </div>
                    )}
                    <FieldError message={errors.scheduled_at} />
                    <div className="ml-auto flex gap-2">
                        <Button type="button" variant="secondary" onClick={() => onOpenChange(false)}>{draft && draftCurrent ? 'Close' : 'Cancel'}</Button>
                        <Button type="button" variant="secondary" onClick={() => void saveDraft()} disabled={draftBusy || (draftCurrent && !!draft)} data-testid="save-email-draft"
                            title="Save this message to send later">
                            {draftBusy ? <Loader2 className="h-4 w-4 animate-spin" /> : draft && draftCurrent ? <Check className="h-4 w-4" /> : <Save className="h-4 w-4" />} {draft && draftCurrent ? 'Saved' : 'Save'}
                        </Button>
                        <Button type="button" variant="secondary" onClick={() => void showPreview()} disabled={previewBusy || !subject.trim() || !body.trim()} data-testid="preview-email">
                            {previewBusy ? <Loader2 className="h-4 w-4 animate-spin" /> : <Eye className="h-4 w-4" />} Preview email
                        </Button>
                        <Button type="submit" disabled={busy || !!tooBig || !!badCc || (schedule && !at)} data-testid="send-submit">
                            {schedule ? <Clock className="h-4 w-4" /> : <Send className="h-4 w-4" />}{busy ? 'Sending…' : schedule ? 'Schedule' : 'Send now'}
                        </Button>
                    </div>
                </div>
            </form>
            <Dialog open={!!preview} onOpenChange={(o) => !o && setPreview(null)} size="xl" title="Email preview"
                description={to.length > 1 ? `As ${to[0].name || to[0].email} will see it. Each person gets their own name and personal link. Nothing has been sent.` : 'As the client will see it. The button uses a sample link here. Nothing has been sent.'}>
                {preview && (
                    <div className="space-y-3" data-testid="email-preview">
                        <dl className="grid grid-cols-[4.5rem_1fr] gap-x-3 gap-y-1 rounded-md bg-stone-50 px-3 py-2 text-sm">
                            {preview.to && <><dt className="text-ink-muted">To</dt><dd className="truncate">{preview.to}</dd></>}
                            <dt className="text-ink-muted">Subject</dt><dd className="font-medium" data-testid="email-preview-subject">{preview.subject}</dd>
                        </dl>
                        <iframe title="Email preview" srcDoc={preview.html} sandbox="" className="h-[65vh] w-full rounded-md border border-line bg-white" data-testid="email-preview-frame" />
                        <div className="flex justify-end gap-2">
                            <Button type="button" variant="secondary" onClick={() => setPreview(null)}>Back to editing</Button>
                        </div>
                    </div>
                )}
            </Dialog>
        </Dialog>
    );
}

