"""Tests RBAC 50 rôles — stdlib pur (unittest), zéro dépendance pip.

Réutilise le validateur JSON-Schema du module Publiciste (Sprint 2) plutôt que
d'en dupliquer un (CLAUDE.md · workflow #5 « éliminer le vieux / ne pas
accumuler de doublons »). L'oracle `jsonschema` est utilisé s'il est présent.

Invariants vérifiés :
  1. `rbac_50_roles.json` valide contre `rbac.schema.json`.
  2. EXACTEMENT 50 rôles (cible non-négociable CLAUDE.md « RBAC 50 rôles »).
  3. `id` et `erpnext_role_name` uniques (pas de collision de rôle Frappe).
  4. Les 5 portails métier de la roadmap S4 sont tous couverts par ≥1 rôle.
  5. Cohérence interne (portail déclaré ⊆ portails connus, cible=50).
  6. Anti-invention (#6) : aucun rôle « own/equipe » n'a le pouvoir
     `set_user_permissions` (élévation de privilège) — réservé au RBAC Admin.
"""

import json
import os
import unittest

# --- localisation des fichiers -------------------------------------------------
HERE = os.path.dirname(os.path.abspath(__file__))
RBAC_DIR = os.path.dirname(HERE)
DELIVERABLES = os.path.dirname(RBAC_DIR)

DATA_PATH = os.path.join(RBAC_DIR, "rbac_50_roles.json")
SCHEMA_PATH = os.path.join(RBAC_DIR, "rbac.schema.json")

# --- réutilisation du validateur Publiciste (pas de duplication) ---------------
import sys
sys.path.insert(0, os.path.join(DELIVERABLES, "publiciste"))
from lib import validator as maison  # noqa: E402


def _load(path):
    with open(path, encoding="utf-8") as fh:
        return json.load(fh)


class RbacContractTest(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.data = _load(DATA_PATH)
        cls.schema = _load(SCHEMA_PATH)
        cls.roles = cls.data["roles"]

    # 1 · conformité au schéma (validateur maison)
    def test_valide_contre_schema_maison(self):
        errors = maison.validate(self.data, self.schema)
        self.assertEqual(errors, [], f"Erreurs de schéma (maison) : {errors}")

    # 1bis · oracle jsonschema si disponible
    def test_valide_contre_oracle_jsonschema_si_present(self):
        try:
            import jsonschema
        except ImportError:
            self.skipTest("jsonschema non installé (validateur maison fait foi)")
        jsonschema.validate(self.data, self.schema)

    # 2 · cible non-négociable = 50
    def test_exactement_50_roles(self):
        self.assertEqual(self.data["cible_rbac_roles"], 50)
        self.assertEqual(len(self.roles), 50, "La cible CLAUDE.md « RBAC 50 rôles » exige exactement 50 rôles.")

    # 3 · unicité des identifiants et des noms de rôle Frappe
    def test_ids_uniques(self):
        ids = [r["id"] for r in self.roles]
        self.assertEqual(len(ids), len(set(ids)), "id de rôle dupliqué.")

    def test_noms_erpnext_uniques(self):
        names = [r["erpnext_role_name"] for r in self.roles]
        self.assertEqual(len(names), len(set(names)), "erpnext_role_name dupliqué (collision Frappe).")

    def test_noms_erpnext_prefixes_oto(self):
        for r in self.roles:
            self.assertTrue(
                r["erpnext_role_name"].startswith("OTO "),
                f"{r['id']}: le rôle Frappe doit être préfixé « OTO » pour éviter la collision avec les rôles natifs.",
            )

    # 4 · les 5 portails métier de la roadmap S4 sont tous couverts
    def test_cinq_portails_business_couverts(self):
        portails_declares = set(self.data["portails_business"])
        self.assertEqual(portails_declares, {"ventes", "construction", "achat", "compta", "direction"})
        portails_utilises = {r["portail"] for r in self.roles}
        for p in portails_declares:
            self.assertIn(p, portails_utilises, f"Portail métier « {p} » sans aucun rôle rattaché.")

    # 5 · cohérence portail
    def test_portail_connu(self):
        connus = {"ventes", "construction", "achat", "compta", "direction", "plateforme"}
        for r in self.roles:
            self.assertIn(r["portail"], connus, f"{r['id']}: portail inconnu {r['portail']!r}")

    # 6 · anti-élévation de privilège : set_user_permissions réservé au groupe
    def test_set_user_permissions_reserve_admin(self):
        for r in self.roles:
            verbs = {a for p in r["permissions_cibles"] for a in p["actions"]}
            if "set_user_permissions" in verbs:
                self.assertEqual(
                    r["scope_donnees"], "groupe",
                    f"{r['id']}: 'set_user_permissions' exige un scope 'groupe' (séparation des pouvoirs).",
                )
                self.assertEqual(
                    r["famille"], "plateforme",
                    f"{r['id']}: seul un rôle 'plateforme' peut porter 'set_user_permissions'.",
                )

    # 7 · chaque rôle a au moins un binding de permission
    def test_chaque_role_a_des_permissions(self):
        for r in self.roles:
            self.assertGreaterEqual(len(r["permissions_cibles"]), 1, f"{r['id']}: aucun binding de permission.")


if __name__ == "__main__":
    unittest.main(verbosity=2)
