import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { router } from '@inertiajs/react';
import { AlertTriangle, ChevronRight, FileCode2, Loader2, Upload } from 'lucide-react';
import { useEffect, useState } from 'react';
import { fmtDateFr, fmtMillimes, fmtTaux, fmtTnd } from './format';

interface Props {
    open: boolean;
    onOpenChange: (open: boolean) => void;
    declarationId: number;
    libellePeriode: string;
    /**
     * Ligne visée, ou `null` pour toute la période.
     *
     * C'est la granularité du portail de TEJ, où chaque validation de formulaire
     * dépose un certificat : elle permet d'envoyer ce qui est prêt sans attendre
     * que tout le mois le soit.
     */
    ligne?: { id: number; nom: string } | null;
}

/** Un certificat du fichier, tel que `TejDeclarationService::recapitulatif()` le rend. */
interface CertificatRecap {
    ligne_id: number;
    reference: string;
    beneficiaire: string | null;
    property_nom: string | null;
    type: string | null;
    identifiant_type: string;
    identifiant: string | null;
    operation_code: string | null;
    operation_libelle: string | null;
    annee_facturation: number | null;
    date_paiement: string | null;
    montant_ht: number;
    taux_tva: number;
    montant_tva: number;
    montant_ttc: number;
    taux: number;
    montant_retenue: number;
    montant_net: number;
}

interface Recap {
    ok: boolean;
    message?: string;
    /** `'0'` = initial, `'1'` = rectificatif — déduit par le serveur, jamais choisi ici. */
    acte?: string;
    acte_libelle?: string;
    periode?: string;
    certificats?: CertificatRecap[];
    totaux?: { ht: number; tva: number; ttc: number; retenue: number; net: number };
    xml?: string;
    octets?: number;
}

/**
 * Dépôt de la déclaration sur TEJ — le seul écran du manager qui produise un
 * acte fiscal irréversible.
 *
 * ⚠️ CE QUE CET ÉCRAN EXISTE POUR EMPÊCHER. Sur le portail de la DGI, le bouton
 * « Valider » du formulaire de certificat appelle directement l'endpoint de
 * dépôt : pas de modale, pas de brouillon, pas de récapitulatif. Le 2026-08-11,
 * un clic a produit une déclaration initiale que personne n'avait relue. Ici, ce
 * qui part est CHARGÉ ET AFFICHÉ avant que le bouton d'envoi n'existe.
 *
 * ═══════════════════════════════════════════════════════════════════════════
 * POURQUOI CE N'EST PLUS LE XML QUI EST MONTRÉ
 * ═══════════════════════════════════════════════════════════════════════════
 * Il l'a été, avec une case « j'ai relu ce fichier ». Relire trois cents lignes
 * de balises pour vérifier un montant n'est pas une relecture : c'est une case
 * qu'on coche. Le récapitulatif porte EXACTEMENT les mêmes données — le serveur
 * les prend sur les lignes que le fichier embarque, et construit ce fichier dans
 * la même méthode, donc un refus de forme tombe ici comme il tomberait à l'envoi.
 *
 * Le XML reste accessible, replié : un dépôt fiscal doit rester vérifiable octet
 * par octet le jour où la question se pose.
 *
 * L'aperçu ne touche pas le réseau de TEJ : il construit le fichier localement.
 * Le charger ne coûte rien et n'engage à rien — c'est pour ça qu'il est en
 * premier.
 */
export function TejDepotDialog({ open, onOpenChange, declarationId, libellePeriode, ligne = null }: Props) {
    const [recap, setRecap] = useState<Recap | null>(null);
    const [chargement, setChargement] = useState(false);
    const [envoi, setEnvoi] = useState(false);
    const [xmlOuvert, setXmlOuvert] = useState(false);

    useEffect(() => {
        if (!open) {
            // Remise à zéro à la FERMETURE : rouvrir la modale doit recharger le
            // récapitulatif. Garder l'état ferait qu'une ligne modifiée entre
            // deux ouvertures serait déposée sur la foi de chiffres périmés.
            setRecap(null);
            setXmlOuvert(false);
            return;
        }

        setChargement(true);
        const urlApercu = ligne
            ? route('retenue.tej.ligne.apercu', { id: declarationId, ligne: ligne.id })
            : route('retenue.tej.apercu', { id: declarationId });

        /*
         * ⚠️ TROIS ÉCHECS DIFFÉRENTS, TROIS MESSAGES DIFFÉRENTS.
         *
         * Ce `.catch()` rendait « Le fichier n'a pas pu être construit » pour
         * TOUT — y compris quand le serveur n'avait pas répondu du tout. Le
         * message accusait donc la déclaration alors que rien n'avait été
         * construit ni même demandé, et a envoyé chercher un défaut de données
         * là où le serveur de développement était simplement arrêté.
         *
         * Un message d'erreur doit dire ce qui a échoué, pas ce qu'on redoutait.
         */
        fetch(urlApercu, {
            headers: { Accept: 'application/json' },
        })
            .then(async (reponse) => {
                if (!reponse.ok) {
                    // Le serveur a répondu, mais pas ce qu'on attend : session
                    // expirée (401/419), droit manquant (403), erreur interne.
                    throw new Error(String(reponse.status));
                }

                return (await reponse.json()) as Recap;
            })
            .then((donnees) => setRecap(donnees))
            .catch((erreur: Error) => {
                const statut = Number(erreur.message);

                setRecap({
                    ok: false,
                    message: Number.isInteger(statut)
                        ? `Le serveur a refusé la demande (HTTP ${statut}). ` +
                          (statut === 401 || statut === 419
                              ? 'Votre session a expiré : rechargez la page et reconnectez-vous.'
                              : statut === 403
                                ? 'Votre compte n’a pas le droit de déposer une déclaration.'
                                : 'Rien n’a été envoyé à la DGI.')
                        : 'Le serveur n’a pas répondu — rien n’a été envoyé à la DGI. ' +
                          'Rechargez la page ; si l’écran reste vide, l’application n’est plus en ligne.',
                });
            })
            .finally(() => setChargement(false));
    }, [open, declarationId, ligne?.id]);

    const deposer = () => {
        setEnvoi(true);
        const urlDepot = ligne
            ? route('retenue.tej.ligne.deposer', { id: declarationId, ligne: ligne.id })
            : route('retenue.tej.deposer', { id: declarationId });

        router.post(
            urlDepot,
            {},
            {
                preserveScroll: true,
                onFinish: () => {
                    setEnvoi(false);
                    onOpenChange(false);
                },
            },
        );
    };

    const certificats = recap?.certificats ?? [];
    const rectificatif = recap?.acte === '1';

    return (
        <Dialog open={open} onOpenChange={onOpenChange}>
            <DialogContent className="flex max-h-[88vh] max-w-3xl flex-col">
                <DialogHeader>
                    <DialogTitle className="flex items-center gap-2">
                        <Upload className="h-5 w-5" />
                        {ligne ? `Envoyer « ${ligne.nom} » sur TEJ` : `Déposer ${libellePeriode} sur TEJ`}
                    </DialogTitle>
                    <DialogDescription>
                        Le dépôt est <strong>définitif</strong> : il ne s'annule pas, il ne se corrige que par une déclaration rectificative. Voici
                        exactement ce qui part.
                    </DialogDescription>
                </DialogHeader>

                {chargement && (
                    <div className="text-muted-foreground flex items-center gap-2 py-8 text-sm">
                        <Loader2 className="h-4 w-4 animate-spin" />
                        Préparation du dépôt…
                    </div>
                )}

                {/* ⚠️ CETTE FENÊTRE EST VERROUILLÉE, ET ELLE DOIT LE RESTER : on
                    ne modifie pas une déclaration pendant qu'on la relit. Mais
                    un refus sans porte de sortie est un cul-de-sac — il fallait
                    fermer, retenir le message, deviner lequel des trois écrans
                    porte le champ manquant, et revenir. Le bandeau de l'écran
                    liste maintenant chaque ligne bloquante AVEC son lien de
                    correction ; ce renvoi-là évite d'avoir à le chercher. */}
                {!chargement && recap && !recap.ok && (
                    <div className="flex flex-col gap-1 rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-900">
                        <span className="flex gap-2">
                            <AlertTriangle className="mt-0.5 h-4 w-4 shrink-0" />
                            <span>{recap.message}</span>
                        </span>
                        <button
                            type="button"
                            onClick={() => onOpenChange(false)}
                            className="w-fit pl-6 text-xs font-medium underline underline-offset-2 hover:text-amber-950"
                        >
                            Fermer et corriger — l'écran liste chaque ligne bloquante et où la réparer
                        </button>
                    </div>
                )}

                {!chargement && recap?.ok && (
                    <div className="-mx-1 flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto px-1 pb-1">
                        {/* ═══════════════════════════════════════════════════
                            L'ACTE, EN TÊTE ET EN TOUTES LETTRES
                            ═══════════════════════════════════════════════════
                            Il n'est plus choisi : le serveur le déduit de l'état
                            de la période (`TejDeclarationService::acteRequis()`).
                            Il doit donc être DIT, sinon on dépose un rectificatif
                            en croyant faire une déclaration — ou l'inverse. */}
                        <div
                            className={
                                rectificatif
                                    ? 'flex flex-col gap-1 rounded-lg border border-amber-200 bg-amber-50 px-3 py-2.5'
                                    : 'flex flex-col gap-1 rounded-lg border px-3 py-2.5'
                            }
                        >
                            <div className="flex flex-wrap items-center gap-2">
                                <Badge variant={rectificatif ? 'default' : 'secondary'} className={rectificatif ? 'bg-amber-600' : undefined}>
                                    {rectificatif ? 'Dépôt rectificatif' : 'Dépôt initial'}
                                </Badge>
                                <span className="text-sm font-medium">{recap.periode}</span>
                            </div>
                            <p className={rectificatif ? 'text-xs text-amber-900' : 'text-muted-foreground text-xs'}>
                                {rectificatif
                                    ? 'Cette période porte déjà des certificats chez la DGI. Ce dépôt les COMPLÈTE : il en ajoute, il ne remplace et n’annule rien.'
                                    : 'Cette période n’a encore rien chez la DGI. Ce dépôt la déclare pour la première fois.'}
                            </p>
                        </div>

                        {/* Le total en quatre chiffres — ce qu'on vérifie d'abord,
                            et ce qu'on ne lisait pas dans le XML. */}
                        <div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
                            {/* La devise est dans l'ÉTIQUETTE, pas dans la valeur :
                                collée au montant, « TND » passait à la ligne et
                                coupait le chiffre en deux dans une tuile étroite. */}
                            <Tuile libelle="Certificats" valeur={String(certificats.length)} detail="dans ce dépôt" />
                            <Tuile libelle="Base TTC (TND)" valeur={fmtMillimes(recap.totaux?.ttc ?? 0)} />
                            <Tuile libelle="Retenue (TND)" valeur={fmtMillimes(recap.totaux?.retenue ?? 0)} accent />
                            <Tuile libelle="Net servi (TND)" valeur={fmtMillimes(recap.totaux?.net ?? 0)} />
                        </div>

                        <div className="flex flex-col gap-2">
                            {certificats.map((certificat) => (
                                <CarteCertificat key={certificat.ligne_id} certificat={certificat} />
                            ))}
                        </div>

                        {/* Le fichier, replié. Il n'est plus ce qu'on relit, mais
                            il reste ce qui part : le cacher pour de bon
                            empêcherait de vérifier une balise le jour où TEJ
                            refuse sans dire pourquoi. */}
                        <Collapsible open={xmlOuvert} onOpenChange={setXmlOuvert}>
                            <CollapsibleTrigger className="text-muted-foreground hover:text-foreground flex items-center gap-1.5 text-xs">
                                <ChevronRight
                                    className={xmlOuvert ? 'h-3.5 w-3.5 rotate-90 transition-transform' : 'h-3.5 w-3.5 transition-transform'}
                                />
                                <FileCode2 className="h-3.5 w-3.5" />
                                Voir le fichier XML envoyé ({recap.octets ?? 0} octets)
                            </CollapsibleTrigger>
                            <CollapsibleContent>
                                <pre className="bg-muted/40 mt-2 max-h-64 overflow-auto rounded-md border p-3 text-xs leading-relaxed">
                                    {recap.xml}
                                </pre>
                            </CollapsibleContent>
                        </Collapsible>
                    </div>
                )}

                <DialogFooter>
                    <Button variant="outline" onClick={() => onOpenChange(false)}>
                        Annuler
                    </Button>
                    {/* Le bouton n'apparaît QUE si le récapitulatif a pu être
                        construit et affiché : on ne dépose pas ce qu'on n'a pas vu. */}
                    {recap?.ok && (
                        <Button onClick={deposer} disabled={envoi}>
                            {envoi ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Upload className="mr-2 h-4 w-4" />}
                            {rectificatif ? 'Déposer en rectificatif' : 'Déposer sur TEJ'}
                        </Button>
                    )}
                </DialogFooter>
            </DialogContent>
        </Dialog>
    );
}

/** Un chiffre du total, lisible d'un coup d'œil. */
function Tuile({ libelle, valeur, detail, accent = false }: { libelle: string; valeur: string; detail?: string; accent?: boolean }) {
    return (
        <div className="flex flex-col gap-0.5 rounded-lg border px-3 py-2">
            <span className="text-muted-foreground text-[11px] tracking-wide uppercase">{libelle}</span>
            <span className={accent ? 'text-sm font-semibold text-amber-700 tabular-nums' : 'text-sm font-semibold tabular-nums'}>{valeur}</span>
            {detail && <span className="text-muted-foreground text-[11px]">{detail}</span>}
        </div>
    );
}

/**
 * Un certificat, carte par carte.
 *
 * L'ordre suit la lecture d'un certificat papier : QUI, POUR QUOI, COMBIEN.
 * Les montants sont ceux du serveur — aucun n'est recalculé ici, sous peine de
 * montrer autre chose que ce qui part.
 */
function CarteCertificat({ certificat }: { certificat: CertificatRecap }) {
    return (
        <div className="flex flex-col gap-2 rounded-lg border px-3 py-2.5">
            <div className="flex flex-wrap items-start justify-between gap-2">
                <div className="flex min-w-0 flex-col">
                    <span className="truncate text-sm font-medium">{certificat.beneficiaire ?? `Ligne #${certificat.ligne_id}`}</span>
                    <span className="text-muted-foreground truncate text-xs">
                        {certificat.identifiant_type} {certificat.identifiant ?? '—'}
                        {certificat.property_nom && <> · {certificat.property_nom}</>}
                    </span>
                </div>
                <div className="flex shrink-0 items-center gap-1.5">
                    {certificat.type && (
                        <Badge variant="outline" className="capitalize">
                            {certificat.type}
                        </Badge>
                    )}
                    <Badge variant="secondary" className="font-mono">
                        {certificat.reference}
                    </Badge>
                </div>
            </div>

            {/* L'opération est ce que TEJ contrôle le plus durement : son code
                porte le taux, et un libellé de 400 caractères ne tient pas sur
                une ligne — d'où le `line-clamp`. */}
            <div className="text-muted-foreground flex flex-wrap items-center gap-x-3 gap-y-0.5 text-xs">
                {certificat.operation_code && (
                    <span className="line-clamp-2">
                        <span className="font-mono">{certificat.operation_code}</span>
                        {certificat.operation_libelle && <> — {certificat.operation_libelle}</>}
                    </span>
                )}
            </div>
            <div className="text-muted-foreground flex flex-wrap items-center gap-x-3 gap-y-0.5 text-xs">
                {certificat.date_paiement && <span>Payé le {fmtDateFr(certificat.date_paiement)}</span>}
                {certificat.annee_facturation && <span>Exercice {certificat.annee_facturation}</span>}
            </div>

            {/* ═══════════════════════════════════════════════════════════════
                LA DÉCOMPOSITION D'ABORD, LA CONCLUSION ENSUITE
                ═══════════════════════════════════════════════════════════════
                Cinq montants alignés en colonnes se lisaient comme un tableau
                sans hiérarchie, et « TND » répété cinq fois faisait passer les
                chiffres à la ligne. Ici, la base est une ligne discrète — c'est
                l'arithmétique, elle se vérifie — et la conclusion, ce qu'on
                retient et ce qu'on paie, tient sa propre ligne.

                Le bloc HT/TVA disparaît quand il n'y a pas de TVA : un loyer
                n'a pas de décomposition, et afficher « TVA 0,00 % 0,000 »
                obligerait à lire une ligne pour constater qu'elle ne dit rien. */}
            <div className="border-t pt-2">
                {certificat.taux_tva > 0 && (
                    <div className="text-muted-foreground flex flex-wrap items-baseline gap-x-2 gap-y-0.5 text-xs">
                        <span>
                            HT <span className="tabular-nums">{fmtMillimes(certificat.montant_ht)}</span>
                        </span>
                        <span>
                            + TVA {fmtTaux(certificat.taux_tva)} <span className="tabular-nums">{fmtMillimes(certificat.montant_tva)}</span>
                        </span>
                        <span>
                            = TTC <span className="tabular-nums">{fmtMillimes(certificat.montant_ttc)}</span>
                        </span>
                    </div>
                )}

                <div className="mt-1 flex flex-wrap items-baseline justify-between gap-x-6 gap-y-1 text-sm">
                    <span className="text-muted-foreground">
                        Base {fmtTnd(certificat.montant_ttc)} · retenue {fmtTaux(certificat.taux)}
                    </span>
                    <span className="flex items-baseline gap-4">
                        <span className="font-semibold text-amber-700 tabular-nums">− {fmtTnd(certificat.montant_retenue)}</span>
                        <span className="text-muted-foreground">
                            net servi <span className="text-foreground font-medium tabular-nums">{fmtTnd(certificat.montant_net)}</span>
                        </span>
                    </span>
                </div>
            </div>
        </div>
    );
}
