"""Construction des fixtures ERPNext 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 contenant les deux
fichiers de fixtures Frappe (`role`, `custom_docperm`) + un manifeste de
traçabilité (contrainte #6 : tout provient du contrat, rien n'est inventé).

Déterminisme : tri stable (rôles par nom Frappe, DocPerms par (role, doctype,
permlevel)) → la sortie est reproductible bit-à-bit à contrat constant, donc
diffable et re-générable en CI sans horodatage volatil.
"""

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_bundle(contract: dict) -> dict[str, Any]:
    """Transforme le contrat RBAC en bundle de fixtures Frappe déterministe."""
    roles = _sorted_roles(contract)

    # --- 1. Fixtures Role (unicité garantie par le contrat, re-vérifiée) ------
    role_fixtures: list[dict] = []
    seen_roles: set[str] = set()
    for r in roles:
        name = r["erpnext_role_name"]
        if name in seen_roles:
            raise ValueError(f"Rôle Frappe dupliqué dans le contrat : {name!r}")
        seen_roles.add(name)
        role_fixtures.append(frappe.role_fixture(name))

    # --- 2. Fixtures Custom DocPerm ------------------------------------------
    docperms: list[dict] = []
    custom_doctypes: set[str] = set()
    for r in roles:
        role_name = r["erpnext_role_name"]
        if_owner = r["scope_donnees"] == frappe.SCOPE_IF_OWNER
        # Tri des DocTypes cibles pour un ordre stable indépendant du contrat.
        for perm in sorted(r["permissions_cibles"], key=lambda p: p["doctype"]):
            doctype = perm["doctype"]
            if perm.get("custom") is True:
                custom_doctypes.add(doctype)
            docperms.append(
                frappe.docperm_fixture(
                    doctype,
                    role_name,
                    perm["actions"],
                    if_owner=if_owner,
                )
            )

    docperms.sort(key=lambda d: (d["role"], d["parent"], d["permlevel"]))

    # --- 3. Manifeste de traçabilité -----------------------------------------
    manifest = {
        "generated_from": "rbac_50_roles.json",
        "source_version": contract["version"],
        "cible_rbac_roles": contract["cible_rbac_roles"],
        "counts": {
            "role": len(role_fixtures),
            "custom_docperm": len(docperms),
            "doctypes_uniques": len({d["parent"] for d in docperms}),
        },
        # DocTypes DTP non natifs : à CRÉER + confirmer sur le VPS avant import
        # des DocPerms qui les ciblent (SPEC §6/§7). Jamais supposés existants.
        "custom_doctypes_a_confirmer": sorted(custom_doctypes),
    }

    return {
        "manifest": manifest,
        "role": role_fixtures,
        "custom_docperm": docperms,
    }
