"""Résolution des rôles du pipeline vente depuis le contrat RBAC 50 rôles.

Réutilisation (workflow #5 : zéro duplication) de l'unique source de vérité des
rôles ERPNext : `05_deliverables_mvp/rbac/rbac_50_roles.json` (validé par
`rbac.schema.json` dans le job CI `rbac-tests`). Le contrat pipeline
(`workflow_vente_spec.json`) ne cite JAMAIS un nom de rôle Frappe en dur : il
référence l'`id` stable d'un rôle RBAC → ici on résout `id → erpnext_role_name`.

Conséquence anti-invention (#6) : un `role_id` absent du contrat RBAC lève une
erreur (aucun rôle fabriqué), et renommer un rôle côté RBAC se propage
automatiquement au workflow sans édition manuelle.
"""

from __future__ import annotations

import json
import os

_HERE = os.path.dirname(os.path.abspath(__file__))
_DELIVERABLES = os.path.normpath(os.path.join(_HERE, "..", "..", ".."))
RBAC_CONTRACT_PATH = os.path.join(_DELIVERABLES, "rbac", "rbac_50_roles.json")


class RoleResolver:
    """Table `role_id → erpnext_role_name` construite depuis le contrat RBAC."""

    def __init__(self, contract: dict) -> None:
        self._by_id: dict[str, dict] = {}
        for role in contract.get("roles", []):
            rid = role.get("id")
            if rid is None:
                raise ValueError("Rôle RBAC sans `id` — contrat corrompu.")
            if rid in self._by_id:
                raise ValueError(f"`id` de rôle RBAC dupliqué : {rid!r}")
            self._by_id[rid] = role
        self._portails = set(contract.get("portails_business", []))

    @classmethod
    def from_path(cls, path: str = RBAC_CONTRACT_PATH) -> "RoleResolver":
        with open(path, encoding="utf-8") as fh:
            return cls(json.load(fh))

    def erpnext_name(self, role_id: str) -> str:
        """`id` RBAC → nom de rôle Frappe. Lève si l'`id` n'existe pas."""
        role = self._by_id.get(role_id)
        if role is None:
            raise KeyError(
                f"role_id {role_id!r} introuvable dans rbac_50_roles.json "
                f"(aucun rôle inventé · #6)."
            )
        name = role.get("erpnext_role_name")
        if not name:
            raise ValueError(f"Rôle {role_id!r} sans `erpnext_role_name`.")
        return name

    def portail(self, role_id: str) -> str | None:
        role = self._by_id.get(role_id)
        return role.get("portail") if role else None

    def known_ids(self) -> frozenset[str]:
        return frozenset(self._by_id)
