"""
ICAC v2.2 — Audit Mandats
Détection de fractionnement de commandes (split procurement).

Seuil MAPA: 40 000€ HT (fournitures/services)
Alerte si ≥3 mandats au même tiers cumulant >30 000€.
Alerte si un article montre des mandats réguliers juste sous seuil.

Fonctionne sur les chunks RAG extraits du Compte de Gestion (CG).
Les CG contiennent la balance des comptes mais PAS le détail par tiers.
On analyse donc les comptes de classe 6 (charges) et les opérations.
"""
import re
import logging
from typing import Optional

log = logging.getLogger("icac.audit_mandats")

# Seuils réglementaires (Code de la Commande Publique)
SEUIL_MAPA_FOURNITURES = 40000  # € HT
SEUIL_MAPA_TRAVAUX = 100000     # € HT
SEUIL_ALERTE_CUMUL = 30000      # Alerte si cumul > ce seuil
SEUIL_ALERTE_NB_MANDATS = 3     # ≥3 opérations au même poste

# Comptes M57 liés à la communication / relations publiques
COMPTES_COMMUNICATION = {
    "6231": "Annonces et insertions",
    "6233": "Foires et expositions",
    "6236": "Catalogues et imprimés",
    "6237": "Publications",
    "6238": "Divers (communication)",
    "623":  "Publicité, publications, relations publiques",
}

# Comptes M57 sensibles (fractionnement fréquent)
COMPTES_SENSIBLES = {
    "6042": "Achats prestations de services",
    "611":  "Sous-traitance générale",
    "6156": "Maintenance",
    "6185": "Frais colloques, séminaires",
    "617":  "Études et recherches",
    "6226": "Honoraires",
    "6228": "Divers (rémunérations intermédiaires)",
    "6231": "Annonces et insertions",
    "6236": "Catalogues et imprimés",
    "6238": "Divers communication",
    "6241": "Transports de biens",
    "6248": "Divers transports",
    "6251": "Voyages et déplacements",
    "6257": "Réceptions",
    "6262": "Frais télécommunications",
    "627":  "Services bancaires",
    "6281": "Concours divers (cotisations)",
}

# Données connues du CG Budget Principal 2024 — Bessèges
# Extraites des images du Compte de Résultat et État de consommation
DONNEES_CG_2024 = {
    "exercice": 2024,
    "total_charges_fonctionnement": 2419705.67,
    "achats_charges_externes": 888860.12,
    "charges_personnel": 1376440.51,
    "charges_intervention": 305849.86,
    "dont_personnes_morales_droit_prive": 77903.00,
    "dont_autres_organismes_publics": 189158.00,
    "charges_financieres": 95537.90,
    "resultat_exercice": 992975.95,
    # Fournisseurs (balance compte 4011)
    "fournisseurs_debit_total": 612859.84,
    "fournisseurs_credit_total": 640960.36,
    # Comptes classe 6 — début visible sur p.41
    "6042_achats_prestations": 40116.60,
    "60611_eau_assainissement": 21203.61,
    "60612_electricite": 137815.64,
}


def extraire_mandats_texte(rag_chunks: list) -> list:
    """
    Extrait les données de mandats/comptes depuis les chunks RAG.
    Chaque chunk est un dict avec 'text', 'source', 'score', etc.

    Retourne une liste de dicts:
    {compte, libelle, montant, source_page}
    """
    mandats = []

    for chunk in rag_chunks:
        text = chunk.get("text", "")
        source = chunk.get("source", "")
        page = chunk.get("page", "?")

        # Chercher les patterns de comptes comptables dans le texte
        # Pattern 1: "6042  Achats de prestations  40 116,60"
        pattern_balance = re.findall(
            r'(\d{3,5})\s+'           # numéro de compte
            r'([A-ZÀ-Ü][a-zà-ü].*?)'  # libellé
            r'\s+([\d\s]+[,\.]\d{2})',  # montant
            text
        )
        for match in pattern_balance:
            compte = match[0].strip()
            libelle = match[1].strip()[:80]
            montant_str = match[2].replace(" ", "").replace(",", ".")
            try:
                montant = float(montant_str)
            except ValueError:
                continue

            # Ne garder que les comptes de classe 6 (charges)
            if compte.startswith("6"):
                mandats.append({
                    "compte": compte,
                    "libelle": libelle,
                    "montant": montant,
                    "source_page": f"{source} p.{page}",
                })

        # Pattern 2: recherche de montants élevés dans le texte brut
        # "communication" + montant
        for keyword in ["communication", "publicité", "relation", "imprimé",
                        "catalogue", "insertion", "annonce"]:
            if keyword in text.lower():
                amounts = re.findall(r'([\d\s]{2,10}[,\.]\d{2})\s*€?', text)
                for amt_str in amounts:
                    clean = amt_str.replace(" ", "").replace(",", ".")
                    try:
                        val = float(clean)
                        if 1000 < val < 500000:  # montant plausible
                            mandats.append({
                                "compte": "623x",
                                "libelle": f"Communication ({keyword})",
                                "montant": val,
                                "source_page": f"{source} p.{page}",
                            })
                    except ValueError:
                        continue

    # Dédupliquer par (compte, montant)
    seen = set()
    unique = []
    for m in mandats:
        key = (m["compte"], m["montant"])
        if key not in seen:
            seen.add(key)
            unique.append(m)

    log.info("audit_mandats: %d entrées extraites des chunks RAG", len(unique))
    return unique


def analyser_tiers(mandats: list) -> dict:
    """
    Analyse les mandats par compte et détecte les anomalies.
    Puisque le CG n'a pas de détail par tiers, on analyse par compte.

    Retourne un dict avec:
    - comptes_detail: liste des comptes avec montants
    - alertes: liste d'alertes de fractionnement
    - total_communication: total comptes 623x
    - resume: texte de résumé
    """
    alertes = []
    comptes = {}

    # Agréger par compte
    for m in mandats:
        cpt = m["compte"]
        if cpt not in comptes:
            comptes[cpt] = {
                "libelle": m["libelle"],
                "total": 0.0,
                "nb_ecritures": 0,
                "sources": [],
            }
        comptes[cpt]["total"] += m["montant"]
        comptes[cpt]["nb_ecritures"] += 1
        if m["source_page"] not in comptes[cpt]["sources"]:
            comptes[cpt]["sources"].append(m["source_page"])

    # Utiliser aussi les données hardcodées du CG
    for cpt_code, montant in [
        ("6042", DONNEES_CG_2024["6042_achats_prestations"]),
        ("60611", DONNEES_CG_2024["60611_eau_assainissement"]),
        ("60612", DONNEES_CG_2024["60612_electricite"]),
    ]:
        if cpt_code not in comptes:
            libelle = COMPTES_SENSIBLES.get(cpt_code, COMPTES_COMMUNICATION.get(cpt_code, ""))
            comptes[cpt_code] = {
                "libelle": libelle,
                "total": montant,
                "nb_ecritures": 1,
                "sources": ["CG 2024 Balance"],
            }

    # Détecter les alertes
    total_comm = 0.0
    for cpt, data in comptes.items():
        # Total communication
        if cpt.startswith("623"):
            total_comm += data["total"]

        # Alerte 1: compte dépassant le seuil d'alerte
        if data["total"] > SEUIL_ALERTE_CUMUL and cpt in COMPTES_SENSIBLES:
            alertes.append({
                "type": "CUMUL_ÉLEVÉ",
                "compte": cpt,
                "libelle": data["libelle"],
                "montant": data["total"],
                "seuil": SEUIL_MAPA_FOURNITURES,
                "message": (
                    f"⚠ Compte {cpt} ({data['libelle']}): "
                    f"{data['total']:,.2f}€ — cumul élevé, "
                    f"vérifier s'il y a eu mise en concurrence"
                ),
            })

        # Alerte 2: montant proche du seuil MAPA (entre 35k et 40k)
        if 35000 < data["total"] < SEUIL_MAPA_FOURNITURES:
            alertes.append({
                "type": "PROCHE_SEUIL",
                "compte": cpt,
                "libelle": data["libelle"],
                "montant": data["total"],
                "seuil": SEUIL_MAPA_FOURNITURES,
                "message": (
                    f"⚠ Compte {cpt} ({data['libelle']}): "
                    f"{data['total']:,.2f}€ — proche du seuil MAPA "
                    f"({SEUIL_MAPA_FOURNITURES:,}€ HT), "
                    f"risque de fractionnement"
                ),
            })

    # Alerte 3: total communication élevé
    if total_comm > 50000:
        alertes.append({
            "type": "COMMUNICATION_ÉLEVÉE",
            "compte": "623x",
            "libelle": "Communication (total)",
            "montant": total_comm,
            "seuil": SEUIL_MAPA_FOURNITURES,
            "message": (
                f"⚠ Total communication (623x): {total_comm:,.2f}€ — "
                f"au-dessus du seuil MAPA, obligation de marché public"
            ),
        })

    # Alerte globale sur les achats/charges externes
    achats = DONNEES_CG_2024["achats_charges_externes"]
    if achats > 800000:
        alertes.append({
            "type": "ACHATS_ÉLEVÉS",
            "compte": "60-62",
            "libelle": "Achats et charges externes",
            "montant": achats,
            "seuil": None,
            "message": (
                f"ℹ Total achats et charges externes: {achats:,.2f}€ — "
                f"représente {achats/DONNEES_CG_2024['total_charges_fonctionnement']*100:.1f}% "
                f"des charges de fonctionnement"
            ),
        })

    return {
        "comptes_detail": comptes,
        "alertes": alertes,
        "total_communication": total_comm,
        "donnees_cg": DONNEES_CG_2024,
    }


def formater_rapport_mandats(analyse: dict, query: str = "") -> str:
    """
    Formate le rapport d'audit pour le LLM.
    """
    comptes = analyse["comptes_detail"]
    alertes = analyse["alertes"]
    cg = analyse["donnees_cg"]
    total_comm = analyse["total_communication"]

    lines = [
        "AUDIT DES DÉPENSES — COMMUNE DE BESSÈGES",
        f"Exercice {cg['exercice']} — Compte de Gestion (CG Budget Principal)",
        f"Source: CG Budget Principal {cg['exercice']} (DGFiP)",
        "",
        "═══ CHIFFRES CLÉS ═══",
        f"  Total charges fonctionnement : {cg['total_charges_fonctionnement']:>12,.2f} €",
        f"  Achats et charges externes   : {cg['achats_charges_externes']:>12,.2f} €",
        f"  Charges de personnel         : {cg['charges_personnel']:>12,.2f} €",
        f"  Charges d'intervention       : {cg['charges_intervention']:>12,.2f} €",
        f"    dont pers. morales droit privé: {cg['dont_personnes_morales_droit_prive']:>10,.2f} €",
        f"    dont organismes publics       : {cg['dont_autres_organismes_publics']:>10,.2f} €",
        f"  Charges financières          : {cg['charges_financieres']:>12,.2f} €",
        f"  Résultat de l'exercice       : {cg['resultat_exercice']:>12,.2f} €",
        "",
        f"  Fournisseurs (c.4011) débit  : {cg['fournisseurs_debit_total']:>12,.2f} €",
        f"  Fournisseurs (c.4011) crédit : {cg['fournisseurs_credit_total']:>12,.2f} €",
        "",
    ]

    # Détail communication si la requête porte dessus
    q_low = query.lower()
    if any(k in q_low for k in ["communication", "119", "publicité", "relation"]):
        lines.append("═══ FOCUS COMMUNICATION / RELATIONS PUBLIQUES ═══")
        lines.append(f"  Comptes 623x (Communication):")
        for cpt, data in sorted(comptes.items()):
            if cpt.startswith("623"):
                lines.append(
                    f"    {cpt} {data['libelle']:<35s} {data['total']:>10,.2f} €"
                )
        if total_comm > 0:
            lines.append(f"  TOTAL COMMUNICATION: {total_comm:>10,.2f} €")
        else:
            lines.append("  ⚠ Aucun détail trouvé dans les chunks RAG pour 623x.")
            lines.append("  Le CG indique 888 860€ en achats et charges externes (total).")
            lines.append("  Le détail par article nécessite le développement du chapitre 623.")
        lines.append("")
        lines.append("  NOTE: Le « 119 000€ de communication » mentionné")
        lines.append("  se trouverait dans le compte 623 (Publicité, publications,")
        lines.append("  relations publiques). Le compte de gestion agrégé ne montre")
        lines.append("  pas le détail par prestataire mais par nature comptable.")
        lines.append("  Pour identifier les prestataires, il faudrait le grand livre")
        lines.append("  détaillé ou l'état des mandats par tiers.")
        lines.append("")

    # Comptes détaillés
    if comptes:
        lines.append("═══ COMPTES ANALYSÉS ═══")
        sorted_comptes = sorted(comptes.items(), key=lambda x: x[1]["total"], reverse=True)
        for cpt, data in sorted_comptes[:15]:
            sensible = " ⚠" if cpt in COMPTES_SENSIBLES else ""
            lines.append(
                f"  {cpt} {data['libelle']:<35s} {data['total']:>10,.2f} €{sensible}"
            )
        lines.append("")

    # Alertes
    if alertes:
        lines.append("═══ ALERTES ET POINTS DE VIGILANCE ═══")
        for a in alertes:
            lines.append(f"  {a['message']}")
        lines.append("")
    else:
        lines.append("═══ AUCUNE ALERTE DÉTECTÉE ═══")
        lines.append("  Les montants analysés ne déclenchent pas d'alerte")
        lines.append("  de fractionnement sur les seuils en vigueur.")
        lines.append("")

    # Recommandations
    lines.append("═══ SEUILS RÉGLEMENTAIRES (rappel) ═══")
    lines.append(f"  MAPA fournitures/services : {SEUIL_MAPA_FOURNITURES:>8,} € HT")
    lines.append(f"  MAPA travaux              : {SEUIL_MAPA_TRAVAUX:>8,} € HT")
    lines.append("  Source: Code de la Commande Publique (CCP)")
    lines.append("  Art. R2122-8: procédure adaptée si > seuils")

    return "\n".join(lines)


def audit_from_rag(rag_chunks: list, query: str = "") -> str:
    """
    Point d'entrée principal: prend les chunks RAG,
    extrait, analyse et formate le rapport.
    """
    mandats = extraire_mandats_texte(rag_chunks)
    analyse = analyser_tiers(mandats)
    rapport = formater_rapport_mandats(analyse, query)
    log.info(
        "audit_mandats: %d comptes, %d alertes, comm=%.2f€",
        len(analyse["comptes_detail"]),
        len(analyse["alertes"]),
        analyse["total_communication"]
    )
    return rapport
