"""Construction des `Role Profile` depuis `rbac_50_roles.json`.

Entrée : le contrat RBAC 50 rôles (déjà validé par `rbac.schema.json` dans le
job CI `rbac-tests`). Sortie : un « bundle » déterministe :
  - role_profile → 1 fixture `Role Profile` par portail distinct du contrat,
    chacun bundlant les `erpnext_role_name` de ses rôles.
  - manifest     → traçabilité (comptes, portails métier vs technique, mapping
    portail → profil), 100 % dérivé du contrat (contrainte #6).

Le contrat porte 6 portails distincts (5 métier listés dans `portails_business`
+ la console technique `plateforme`). Chaque rôle a EXACTEMENT un `portail` →
la couverture des 50 rôles par les profils est bijective (invariant vérifié par
le CLI). La source de vérité des comptes est le JSON, pas la doc (SPEC §3).

Déterminisme : profils triés par clé de portail, rôles triés par nom Frappe →
sortie reproductible bit-à-bit à contrat constant (diffable, re-générable en CI
sans horodatage volatil).
"""

from __future__ import annotations

from typing import Any

from . import frappe


def _roles_by_portail(contract: dict) -> dict[str, list[str]]:
    """Regroupe les `erpnext_role_name` par `portail` (ordre trié, stable)."""
    grouped: dict[str, list[str]] = {}
    for r in contract["roles"]:
        portail = r["portail"]
        name = r["erpnext_role_name"]
        grouped.setdefault(portail, []).append(name)
    for portail in grouped:
        names = grouped[portail]
        if len(names) != len(set(names)):
            raise ValueError(f"Rôle dupliqué dans le portail {portail!r}.")
        names.sort()
    return grouped


def build_bundle(contract: dict) -> dict[str, Any]:
    """Transforme le contrat RBAC en bundle de `Role Profile` déterministe."""
    grouped = _roles_by_portail(contract)
    portails_metier = set(contract.get("portails_business", []))

    role_profiles: list[dict] = []
    profiles_meta: list[dict] = []
    for portail in sorted(grouped):
        role_names = grouped[portail]
        role_profiles.append(frappe.role_profile_fixture(portail, role_names))
        profiles_meta.append(
            {
                "portail": portail,
                "role_profile": frappe.role_profile_name(portail),
                "metier": portail in portails_metier,
                "nb_roles": len(role_names),
            }
        )

    nb_metier = sum(1 for p in profiles_meta if p["metier"])
    manifest = {
        "generated_from": "rbac_50_roles.json",
        "source_version": contract["version"],
        "cible_rbac_roles": contract["cible_rbac_roles"],
        "counts": {
            "role_profiles": len(role_profiles),
            "portails_metier": nb_metier,
            "portails_techniques": len(role_profiles) - nb_metier,
            "roles_couverts": sum(p["nb_roles"] for p in profiles_meta),
        },
        # Mapping portail → profil (métier vs console technique `plateforme`),
        # pour que l'agent ERPNext assigne le bon profil à chaque utilisateur.
        "profiles": profiles_meta,
    }

    return {
        "manifest": manifest,
        "role_profile": role_profiles,
    }
