"""Construction du plan `User Permission` 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 « plan » déterministe qui, pour CHAQUE rôle,
indique le mécanisme Frappe d'enforcement row-level de son `scope_donnees` et,
le cas échéant, un template `User Permission` (allow=Company) à matérialiser par
utilisateur côté VPS.

Déterminisme : tri stable des entrées par nom de rôle Frappe → sortie
reproductible bit-à-bit à contrat constant (diffable, re-générable en CI).

Anti-invention (#6) : 100 % des données proviennent du contrat (nom du rôle,
scope, entité). Aucun utilisateur n'est inventé (sentinelle `USER_PLACEHOLDER`).
Les rôles `equipe` (pas de dimension row-level native) ne reçoivent AUCUN
template et sont signalés « à confirmer VPS » plutôt que mappés arbitrairement.
"""

from __future__ import annotations

from typing import Any

from . import frappe


def _sorted_roles(contract: dict) -> list[dict]:
    return sorted(contract["roles"], key=lambda r: r["erpnext_role_name"])


def build_plan(contract: dict) -> dict[str, Any]:
    """Transforme le contrat RBAC en plan `User Permission` déterministe."""
    roles = _sorted_roles(contract)

    plan: list[dict] = []
    companies: set[str] = set()          # for_value distincts (portée entite)
    equipe_roles: list[str] = []         # rôles equipe → à confirmer VPS
    by_mechanism: dict[str, int] = {m: 0 for m in frappe.SCOPE_MECHANISM.values()}

    for r in roles:
        role_name = r["erpnext_role_name"]
        scope = r["scope_donnees"]
        entite = r["entite_principale"]
        mechanism = frappe.mechanism_for(scope)
        by_mechanism[mechanism] += 1

        template = None
        if mechanism == frappe.MECH_WITH_TEMPLATE:
            # Lève ValueError si le contrat couple `entite` à un périmètre
            # consolidé (« Groupe ») — garde-fou anti-contrat corrompu.
            template = frappe.user_permission_template(entite)
            companies.add(entite)
        elif scope == "equipe":
            equipe_roles.append(role_name)

        plan.append(
            {
                "erpnext_role_name": role_name,
                "scope_donnees": scope,
                "entite_principale": entite,
                "mechanism": mechanism,
                "user_permission_template": template,
            }
        )

    manifest = {
        "generated_from": "rbac_50_roles.json",
        "source_version": contract["version"],
        "cible_rbac_roles": contract["cible_rbac_roles"],
        "counts": {
            "plan_entries": len(plan),
            "user_permission_templates": by_mechanism[frappe.MECH_WITH_TEMPLATE],
            "by_mechanism": by_mechanism,
        },
        # Companies ERPNext à CRÉER/confirmer sur le VPS avant d'appliquer les
        # User Permission (les noms viennent de entite_principale du contrat).
        "companies_a_confirmer": sorted(companies),
        # Rôles `equipe` : aucune dimension row-level native → l'agent ERPNext
        # doit décider du mécanisme (champ custom + User Permission, ou
        # hiérarchie Employee.reports_to). Jamais mappé en douce ici (#6).
        "roles_scope_equipe_a_confirmer": sorted(equipe_roles),
    }

    return {"manifest": manifest, "plan": plan}
