"""Arbitrages entre sources divergentes.

Deux mécanismes distincts :

1. **Les cinq verdicts de la validation de phase 5** — des désaccords déjà
   tranchés sur sources primaires, que le seed doit refléter. Le §7 du cahier
   des charges en fait des critères d'acceptation : la loi « Riposte » doit
   porter l'amende forfaitaire à **500 €** et non 1 500 € (montant abandonné
   après la première lecture au Sénat), et la Guadeloupe ne doit **pas**
   figurer parmi les territoires concernés par les sénatoriales de 2026.

2. **La hiérarchie des sources** pour le pipeline de mise à jour : Journal
   officiel et Légifrance priment sur l'Assemblée et le Sénat, qui priment sur
   le Conseil constitutionnel, qui prime sur la presse. Déclarée dans
   `pipeline/config.yaml`, appliquée par `update.py`.
"""

from __future__ import annotations

import functools
import re
from dataclasses import dataclass, field
from urllib.parse import urlparse

import yaml

from pipeline import chemins
from pipeline.journal import logger
from pipeline.parseurs.markdown import extraire_tableaux, nettoyer

log = logger("conflits")


# ─────────────────────────────────────────────────────────────────────────────
# 1. Verdicts de la validation de phase 5
# ─────────────────────────────────────────────────────────────────────────────
@dataclass(slots=True)
class Verdict:
    """Un désaccord tranché sur source primaire."""

    numero: int
    objet: str
    statut: str          # « TRANCHÉ »
    reponse: str
    textes_concernes: list[str] = field(default_factory=list)

    @property
    def point_cle(self) -> str:
        return f"Arbitrage (validation du 25 juillet 2026) : {self.reponse}"


# Rattachement de chaque verdict aux textes qu'il concerne. La table est
# déclarative et non devinée : chaque ligne cite l'objet du désaccord tel qu'il
# figure dans `lois-2026_phase5_validation.md`.
TEXTES_PAR_VERDICT: dict[int, list[str]] = {
    # « AFD "participation" free party : 500 € (A) ou 1 500 € (B) ? »
    1: ["loi-riposte"],
    # « AFD "inhalation" N₂O : 200 € ou 500 € ? »
    2: ["loi-riposte"],
    # « Saisine CC réellement déposée au 25/07 ? Loi promulguée ? »
    3: ["loi-riposte"],
    # « Guadeloupe concernée par les sénatoriales du 27/09/2026 ? »
    # Aucun texte de la base : l'arbitrage porte sur une échéance du calendrier.
    4: [],
    # « Autres lois promulguées entre le 21 et le 25/07/2026 ? »
    # Porte sur l'exhaustivité de l'inventaire, pas sur un texte en particulier.
    5: [],
}


def lire_verdicts(markdown: str) -> list[Verdict]:
    """Lit la synthèse des verdicts de la validation de phase 5."""
    verdicts: list[Verdict] = []

    for tableau in extraire_tableaux(markdown):
        if not tableau.entetes or tableau.entetes[0] != "#":
            continue
        for ligne in tableau.lignes:
            numero_brut = nettoyer(ligne["#"])
            if not numero_brut.isdigit():
                continue
            numero = int(numero_brut)
            verdicts.append(
                Verdict(
                    numero=numero,
                    objet=nettoyer(ligne.get("Objet du désaccord", "")),
                    statut=nettoyer(ligne.get("Verdict", "")),
                    reponse=nettoyer(ligne.get("Réponse tranchée", "")),
                    textes_concernes=TEXTES_PAR_VERDICT.get(numero, []),
                )
            )

    log.info("verdicts de validation lus", verdicts=len(verdicts))
    return verdicts


# Précisions chiffrées à porter en points clés, reprises verbatim des claims
# décisifs de la validation. Elles répondent au §7 du cahier des charges, qui
# exige de retrouver « AFD 500 € » sur la loi « Riposte ».
PRECISIONS_RIPOSTE: tuple[str, ...] = (
    "Participation à une free party non autorisée : 6 mois d'emprisonnement et "
    "7 500 € d'amende ; amende forfaitaire délictuelle (AFD) de 500 € "
    "(minorée 400 €, majorée 1 000 €) — art. L. 211-15-4 du code de la "
    "sécurité intérieure. Le montant de 1 500 € était la version votée par le "
    "Sénat en première lecture, abandonnée.",
    "Inhalation de protoxyde d'azote : 1 an d'emprisonnement et 3 750 € "
    "d'amende ; AFD de 500 € — art. L. 3611-4 du code de la santé publique.",
    "Autres amendes forfaitaires délictuelles du texte : détention et transport "
    "500 €, vente illicite 800 €.",
)

PRECISIONS_PAR_TEXTE: dict[str, tuple[str, ...]] = {
    "loi-riposte": PRECISIONS_RIPOSTE,
}


def appliquer(textes: dict, verdicts: list[Verdict]) -> int:
    """Porte les arbitrages dans les points clés des textes concernés.

    Retourne le nombre de textes modifiés.
    """
    modifies = 0

    for verdict in verdicts:
        for texte_id in verdict.textes_concernes:
            texte = textes.get(texte_id)
            if texte is None:
                log.warning("verdict orphelin", verdict=verdict.numero, texte=texte_id)
                continue
            if verdict.point_cle not in texte.points_cles:
                texte.points_cles.append(verdict.point_cle)
                modifies += 1

    for texte_id, precisions in PRECISIONS_PAR_TEXTE.items():
        texte = textes.get(texte_id)
        if texte is None:
            continue
        for precision in precisions:
            if precision not in texte.points_cles:
                texte.points_cles.append(precision)

    return modifies


# ─────────────────────────────────────────────────────────────────────────────
# 2. Hiérarchie des sources pour la mise à jour
# ─────────────────────────────────────────────────────────────────────────────
@functools.lru_cache(maxsize=1)
def _hierarchie() -> list[str]:
    configuration = yaml.safe_load(chemins.CONFIG.read_text(encoding="utf-8"))
    return list(configuration.get("hierarchie_sources", []))


def rang_de_source(url: str) -> int:
    """Rang d'autorité d'une URL : plus il est bas, plus la source prime.

    Les hôtes inconnus — presse et analyses — reçoivent le rang le plus faible.
    """
    hote = (urlparse(url).hostname or "").lower().removeprefix("www.")
    for rang, reference in enumerate(_hierarchie()):
        if hote == reference or hote.endswith("." + reference):
            return rang
    return len(_hierarchie())


def trancher(candidats: list[tuple[str, str]]) -> str | None:
    """Choisit une valeur parmi des couples (valeur, url) divergents.

    La valeur retenue est celle qu'affirme la source la plus autorisée. À
    autorité égale, la valeur majoritaire l'emporte ; à égalité parfaite, aucune
    n'est retenue — le pipeline signale plutôt que de trancher au hasard.
    """
    if not candidats:
        return None

    par_valeur: dict[str, tuple[int, int]] = {}
    for valeur, url in candidats:
        rang = rang_de_source(url)
        meilleur_rang, occurrences = par_valeur.get(valeur, (len(_hierarchie()) + 1, 0))
        par_valeur[valeur] = (min(meilleur_rang, rang), occurrences + 1)

    classement = sorted(par_valeur.items(), key=lambda item: (item[1][0], -item[1][1]))
    if len(classement) > 1 and classement[0][1] == classement[1][1]:
        return None
    return classement[0][0]


_MONTANT = re.compile(r"(\d[\d\s ]*)\s*(?:€|euros)", re.IGNORECASE)


def montants_cites(fragment: str) -> list[int]:
    """Montants en euros cités dans un fragment, pour les contrôles d'acceptation."""
    montants: list[int] = []
    for brut in _MONTANT.findall(fragment):
        chiffres = re.sub(r"\D", "", brut)
        if chiffres:
            montants.append(int(chiffres))
    return montants
