"""Parseur de l'annexe récapitulative.

Ces tests portent sur le corpus réel : ils échouent si le format d'entrée
change, ce qui est le comportement voulu — un seed silencieusement incomplet
serait pire qu'une erreur bruyante.
"""

from __future__ import annotations

from datetime import date

import pytest

from pipeline.modeles import (
    Pertinence,
    RegimeLibertes,
    ResultatCC,
    Statut,
    Theme,
    TypeTexte,
)
from pipeline.parseurs.tableaux_sec10 import (
    _deduire_themes,
    _lire_pertinence,
    _lire_regime,
    _lire_statut,
    extraire_marqueurs_annexe,
)

# ─────────────────────────────────────────────────────────────────────────────
# Volumétrie
# ─────────────────────────────────────────────────────────────────────────────


def test_aucun_avertissement(textes_annexe) -> None:
    assert textes_annexe.avertissements == []


def test_volumetrie_minimale(textes_annexe) -> None:
    # 27 lois promulguées + 23 lignes du tableau B, dont 2 éclatées en deux
    # textes chacune : 27 + 21 + 4 = 52.
    assert len(textes_annexe) == 52
    assert len(textes_annexe) >= 49, "le cahier des charges exige au moins 49 textes"


def test_identifiants_uniques(textes_annexe) -> None:
    identifiants = [t.id for t in textes_annexe.textes]
    assert len(identifiants) == len(set(identifiants))


def test_toutes_les_lois_promulguees_ont_numero_et_date(textes_annexe) -> None:
    promulguees = [t for t in textes_annexe.textes if t.statut is Statut.PROMULGUEE]
    assert len(promulguees) == 27
    for texte in promulguees:
        assert texte.numero_officiel, texte.id
        assert texte.date_promulgation, texte.id
        assert texte.regime_libertes is not None, texte.id
        assert texte.guadeloupe_pertinence is not None, texte.id


def test_aucun_texte_non_promulgue_ne_porte_de_numero(textes_annexe) -> None:
    for texte in textes_annexe.textes:
        if texte.statut is not Statut.PROMULGUEE:
            assert texte.numero_officiel is None, texte.id


# ─────────────────────────────────────────────────────────────────────────────
# Contrôles d'acceptation du §7 du cahier des charges
# ─────────────────────────────────────────────────────────────────────────────


def test_loi_chlordecone(par_identifiant) -> None:
    texte = par_identifiant["loi-2026-491"]
    assert texte.numero_officiel == "2026-491"
    assert texte.statut is Statut.PROMULGUEE
    assert texte.date_promulgation == date(2026, 6, 12)
    assert texte.guadeloupe_pertinence is Pertinence.FORTE
    assert {Theme.SANTE, Theme.OUTRE_MER, Theme.MEMOIRE_PATRIMOINE} <= set(texte.themes)


def test_loi_fraudes_et_sa_decision_cc(par_identifiant) -> None:
    texte = par_identifiant["loi-2026-534"]
    assert texte.statut is Statut.PROMULGUEE
    assert texte.date_promulgation == date(2026, 6, 25)
    assert [d.numero_affaire for d in texte.decisions_cc] == ["2026-904 DC"]
    assert texte.guadeloupe_pertinence is Pertinence.FORTE


def test_loi_riposte_statut_et_evenement_de_saisine(par_identifiant) -> None:
    """Le §7 impose le statut `adoptee_non_promulguee` ET l'événement de saisine."""
    texte = par_identifiant["loi-riposte"]
    assert texte.statut is Statut.ADOPTEE_NON_PROMULGUEE
    assert texte.numero_officiel is None

    saisines = [e for e in texte.evenements if e.type_etape == "saisine_cc"]
    assert len(saisines) == 1
    assert "2026-915 DC" in saisines[0].description
    assert "24 juillet 2026" in saisines[0].description
    assert saisines[0].date_evenement == date(2026, 7, 24)

    (affaire,) = texte.decisions_cc
    assert affaire.numero_affaire == "2026-915 DC"
    assert affaire.resultat is ResultatCC.EN_INSTANCE
    assert affaire.date_saisine == date(2026, 7, 24)


def test_ppl_aide_a_mourir(par_identifiant) -> None:
    texte = par_identifiant["ppl-aide-a-mourir"]
    assert texte.type is TypeTexte.PPL
    assert texte.statut is Statut.ADOPTEE_NON_PROMULGUEE
    assert texte.date_adoption == date(2026, 7, 15)
    (affaire,) = texte.decisions_cc
    assert affaire.numero_affaire == "2026-910 DC"
    # Le corpus ne date pas cette saisine : rien ne doit être inventé.
    assert affaire.date_saisine is None
    assert affaire.date_decision_attendue == date(2026, 8, 20)


def test_pjl_pacte_migration(par_identifiant) -> None:
    texte = par_identifiant["pjl-pacte-migration"]
    assert texte.type is TypeTexte.PJL
    assert texte.statut is Statut.NAVETTE
    assert Theme.MIGRATION in texte.themes


def test_loi_simplification_economique(par_identifiant) -> None:
    texte = par_identifiant["loi-2026-403"]
    assert texte.statut is Statut.PROMULGUEE
    (affaire,) = texte.decisions_cc
    assert affaire.numero_affaire == "2026-903 DC"
    assert affaire.resultat is ResultatCC.NON_CONFORMITE_PARTIELLE
    assert "25 articles censurés" in (affaire.resume or "")
    assert texte.guadeloupe_pertinence is Pertinence.FORTE


def test_loi_philippine_est_validee_sans_fausse_saisine(par_identifiant) -> None:
    """Sa cellule cite une décision, pas une saisine : aucun événement de saisine."""
    texte = par_identifiant["loi-philippine"]
    assert texte.statut is Statut.VALIDEE_CC
    assert [e.type_etape for e in texte.evenements if e.type_etape == "saisine_cc"] == []
    decisions = [e for e in texte.evenements if e.type_etape == "decision_cc"]
    assert decisions and decisions[0].date_evenement == date(2026, 7, 23)


def test_programmation_militaire_saisie_sans_numero_d_affaire(par_identifiant) -> None:
    """« Conseil constitutionnel saisi le 6 juill. » : événement, pas d'affaire."""
    texte = par_identifiant["loi-programmation-militaire"]
    saisines = [e for e in texte.evenements if e.type_etape == "saisine_cc"]
    assert len(saisines) == 1
    assert saisines[0].date_evenement == date(2026, 7, 6)
    assert texte.decisions_cc == []


# ─────────────────────────────────────────────────────────────────────────────
# Éclatement des lignes doubles
# ─────────────────────────────────────────────────────────────────────────────


def test_accords_bilateraux_eclates(par_identifiant) -> None:
    kazakhstan = par_identifiant["accord-kazakhstan-readmission"]
    colombie = par_identifiant["accord-colombie-extradition"]
    assert kazakhstan.type is TypeTexte.ACCORD_INTERNATIONAL
    assert kazakhstan.date_adoption == date(2026, 1, 28)
    assert colombie.date_adoption == date(2026, 2, 18)


def test_macf_eclate_en_deux_statuts(par_identifiant) -> None:
    """La ligne MACF décrit deux textes de statuts différents."""
    ratification = par_identifiant["pjl-ratification-ordonnance-macf"]
    accord = par_identifiant["accord-macf-plateforme-centrale"]
    assert ratification.statut is Statut.DEPOSEE_NON_EXAMINEE
    assert accord.statut is Statut.ANNONCEE


# ─────────────────────────────────────────────────────────────────────────────
# Lecture de cellules
# ─────────────────────────────────────────────────────────────────────────────


@pytest.mark.parametrize(
    ("cellule", "attendu"),
    [
        ("**Droits+** : contrôle externe de la détention", RegimeLibertes.DROITS_PLUS),
        ("**Contrôle+** : durées réduites", RegimeLibertes.CONTROLE_PLUS),
        ("Mixte : anti-impayés ; vigilance débiteur", RegimeLibertes.MIXTE),
        ("Neutre", RegimeLibertes.NEUTRE),
        ("Neutre à **droits+** : équité territoriale", RegimeLibertes.DROITS_PLUS),
    ],
)
def test_lire_regime(cellule: str, attendu: RegimeLibertes) -> None:
    regime, note = _lire_regime(cellule)
    assert regime is attendu
    assert note


@pytest.mark.parametrize(
    ("cellule", "attendu", "note"),
    [
        ("Forte — maison d'arrêt de Baie-Mahault", Pertinence.FORTE, "maison d'arrêt"),
        ("Moyenne — CPCE applicable aux DROM", Pertinence.MOYENNE, "CPCE"),
        ("Faible — sans objet", Pertinence.FAIBLE, "sans objet"),
    ],
)
def test_lire_pertinence(cellule: str, attendu: Pertinence, note: str) -> None:
    pertinence, motif = _lire_pertinence(cellule)
    assert pertinence is attendu
    assert note in (motif or "")


@pytest.mark.parametrize(
    ("cellule", "attendu"),
    [
        ("**Adoptée, non promulguée** (21 juill., CMP)", Statut.ADOPTEE_NON_PROMULGUEE),
        ("**En navette** : Sénat 1re lecture 20 mai", Statut.NAVETTE),
        ("**Déposé, non examiné** : Sénat n° 890", Statut.DEPOSEE_NON_EXAMINEE),
        ("**Annoncé** : transmis au Conseil d'État", Statut.ANNONCEE),
        ("**Adoptée par une chambre** : Sénat 5-6 mai", Statut.NAVETTE),
        ("**Adoptée en 1re lecture** : AN 28 mai", Statut.NAVETTE),
        ("**Adoptée** (15-16 juin) ; conforme avec réserves ; **promulgation pendante**",
         Statut.VALIDEE_CC),
    ],
)
def test_lire_statut(cellule: str, attendu: Statut) -> None:
    assert _lire_statut(cellule) is attendu


def test_lire_statut_refuse_l_inconnu() -> None:
    with pytest.raises(ValueError, match="statut non reconnu"):
        _lire_statut("Texte au statut fantaisiste")


def test_themes_ne_confondent_pas_les_sous_chaines() -> None:
    """« eau » ne doit pas se déclencher sur « réseaux », « drapeau », « Retailleau »."""
    assert Theme.ENVIRONNEMENT not in _deduire_themes("PPL réseaux sociaux et mineurs")
    assert Theme.ENVIRONNEMENT not in _deduire_themes("outrage à l'hymne et au drapeau")
    assert Theme.ENVIRONNEMENT not in _deduire_themes("PPL entrisme Retailleau")
    # Contrôle positif : le mot isolé compte bien.
    assert Theme.ENVIRONNEMENT in _deduire_themes("habilitation Martinique énergie et eau")


def test_themes_plafonnes_a_quatre() -> None:
    long_texte = " ".join(
        ["justice", "sécurité", "numérique", "social", "fiscal", "agriculture", "santé"]
    )
    assert len(_deduire_themes(long_texte)) <= 4


# ─────────────────────────────────────────────────────────────────────────────
# Marqueurs de citation
# ─────────────────────────────────────────────────────────────────────────────


def test_marqueurs_de_l_annexe(annexe: str) -> None:
    associations = extraire_marqueurs_annexe(annexe)
    assert "2026-491" in associations
    assert "dim07-24" in associations["2026-491"]
    riposte = next(cle for cle in associations if cle.startswith("Loi « Riposte »"))
    assert "phase5-3.1" in associations[riposte]


# ─────────────────────────────────────────────────────────────────────────────
# Écriture en base
# ─────────────────────────────────────────────────────────────────────────────


def test_seed_en_base(base_ensemencee) -> None:
    cx = base_ensemencee
    assert cx.execute("SELECT COUNT(*) FROM textes").fetchone()[0] == 52
    assert cx.execute("SELECT COUNT(*) FROM decisions_cc").fetchone()[0] == 12


def test_facette_devant_cc(base_ensemencee) -> None:
    """Le §8.7 attend au moins 7 textes suspendus au Conseil constitutionnel."""
    total = base_ensemencee.execute(
        "SELECT COUNT(*) FROM v_textes WHERE devant_cc = 1"
    ).fetchone()[0]
    assert total >= 7


def test_les_lois_promulguees_ne_sont_pas_devant_le_cc(base_ensemencee) -> None:
    """Une décision rendue mais non datée ne doit pas passer pour une instance."""
    restantes = base_ensemencee.execute(
        "SELECT id FROM v_textes WHERE devant_cc = 1 AND statut = 'promulguee'"
    ).fetchall()
    assert restantes == []


def test_recherche_plein_texte(base_ensemencee) -> None:
    """La recherche doit trouver « chlordécone » sans les accents."""
    lignes = base_ensemencee.execute(
        "SELECT t.id FROM textes_fts f JOIN textes t ON t.rowid = f.rowid "
        "WHERE textes_fts MATCH 'chlordecone'"
    ).fetchall()
    assert [ligne["id"] for ligne in lignes] == ["loi-2026-491"]


def test_ecriture_idempotente(base, textes_annexe) -> None:
    """Rejouer le seed ne duplique ni textes, ni sources, ni événements."""
    from pipeline import db

    db.enregistrer_textes(base, textes_annexe.textes)
    premier = db.statistiques(base)
    db.enregistrer_textes(base, textes_annexe.textes)
    second = db.statistiques(base)
    assert premier == second
