"""Synthetic permission-policy model; not an Odoo module or Odoo test runner.

Run: python3 permission-model.py
Only Python standard library. No database, network, Odoo or native converter.
The policy uses invented records and already-expanded effective groups.
"""
from dataclasses import dataclass
import json


@dataclass(frozen=True)
class Actor:
    uid: int
    memberships: tuple
    groups: frozenset


@dataclass(frozen=True)
class Record:
    key: str
    company: str | None
    owner: int


def context_companies(actor, requested):
    # Model the explicitly documented normal-user context fallback.
    if requested:
        if set(requested) - set(actor.memberships):
            raise PermissionError("unauthorized_company_context")
        return tuple(requested)
    return actor.memberships


def active_company(record, actor, active):
    return record.company in active


def shared_or_active(record, actor, active):
    return record.company is None or record.company in active


def own(record, actor, active):
    return record.owner == actor.uid


def any_record(record, actor, active):
    return True


def company_a(record, actor, active):
    return record.company == "A"


def company_b(record, actor, active):
    return record.company == "B"


# (effective group, applicable operations, record predicate).
OWN = ("own_reader", frozenset({"read"}), own)
BROAD = ("broad_reader", frozenset({"read"}), any_record)


def allowed(actor, record, operation, requested, grants=(OWN,),
            restrictions=(active_company,)):
    active = context_companies(actor, requested)
    grant = any(group in actor.groups and operation in operations
                and predicate(record, actor, active)
                for group, operations, predicate in grants)
    return grant and all(rule(record, actor, active) for rule in restrictions)


def old_two_layer_model(actor, record, operation, requested):
    # Deliberately small OLD policy: broad-reader model ACL plus an own-reader
    # record rule for a user who belongs to both groups.
    active = context_companies(actor, requested)
    acl = "broad_reader" in actor.groups and operation == "read"
    group_rules = [own] if "own_reader" in actor.groups and operation == "read" else []
    record_rules = not group_rules or any(r(record, actor, active) for r in group_rules)
    return acl and active_company(record, actor, active) and record_rules


def main():
    reader = Actor(7, ("A", "B"), frozenset({"own_reader"}))
    broad = Actor(7, ("A", "B"), frozenset({"own_reader", "broad_reader"}))
    single = Actor(7, ("A",), frozenset({"own_reader"}))
    none = Actor(7, ("A", "B"), frozenset())
    a_own = Record("A-OWN", "A", 7)
    a_other = Record("A-OTHER", "A", 8)
    b_own = Record("B-OWN", "B", 7)
    shared = Record("SHARED", None, 7)
    cases = []

    def check(name, expected, action):
        try:
            observed = action()
        except PermissionError as error:
            observed = {"error": str(error)}
        assert observed == expected, (name, expected, observed)
        cases.append(dict(case=name, expected=expected, observed=observed))

    check("own_record_active_a", True,
          lambda: allowed(reader, a_own, "read", ("A",)))
    check("other_owner_active_a", False,
          lambda: allowed(reader, a_other, "read", ("A",)))
    check("authorized_b_not_activated", False,
          lambda: allowed(reader, b_own, "read", ("A",)))
    check("authorized_b_activated", True,
          lambda: allowed(reader, b_own, "read", ("A", "B")))
    check("activate_unassigned_b", {"error": "unauthorized_company_context"},
          lambda: allowed(single, b_own, "read", ("B",)))
    check("extra_group_expands_records", True,
          lambda: allowed(broad, a_other, "read", ("A",), grants=(OWN, BROAD)))
    check("global_company_still_limits_broad_group", False,
          lambda: allowed(broad, b_own, "read", ("A",), grants=(OWN, BROAD)))
    check("omitted_global_company_restriction", True,
          lambda: allowed(broad, b_own, "read", ("A",),
                          grants=(OWN, BROAD), restrictions=()))
    check("conflicting_global_restrictions", False,
          lambda: allowed(reader, a_own, "read", ("A", "B"),
                          restrictions=(company_a, company_b)))
    check("shared_record_requires_explicit_policy",
          {"strict": False, "shared_allowed": True},
          lambda: {"strict": allowed(reader, shared, "read", ("A",)),
                   "shared_allowed": allowed(reader, shared, "read", ("A",),
                                             restrictions=(shared_or_active,))})
    check("global_restriction_alone_grants_nothing", False,
          lambda: allowed(none, a_own, "read", ("A",)))
    check("operations_are_separate",
          {"read": True, "write": False, "delete": False},
          lambda: {op: allowed(reader, a_own, op, ("A",))
                   for op in ("read", "write", "delete")})
    check("naive_conversion_expands_old_policy",
          {"old_policy": False, "naive_rewrite": True},
          lambda: {"old_policy": old_two_layer_model(broad, a_other, "read", ("A",)),
                   "naive_rewrite": allowed(broad, a_other, "read", ("A",),
                                             grants=(OWN, BROAD))})
    values = {"A": "100.00", "B": "120.00"}
    check("visibility_does_not_select_company_value",
          {"record_b_readable": True, "value_with_a": "100.00",
           "value_with_b": "120.00"},
          lambda: {"record_b_readable": allowed(reader, b_own, "read", ("A", "B")),
                   "value_with_a": values[context_companies(reader, ("A", "B"))[0]],
                   "value_with_b": values[context_companies(reader, ("B", "A"))[0]]})
    check("visibility_does_not_validate_relation_company",
          {"access_allowed": True, "relation_consistent": False},
          lambda: {"access_allowed": allowed(reader, a_own, "read", ("A", "B")),
                   "relation_consistent": a_own.company == b_own.company})
    check("empty_context_is_fallback_not_deny_all",
          {"active": ["A", "B"], "record_b_readable": True},
          lambda: {"active": list(context_companies(reader, ())),
                   "record_b_readable": allowed(reader, b_own, "read", ())})
    output = dict(kind="executed synthetic permission-policy model",
                  run_on="2026-11-05", runtime="Python standard library",
                  case_count=len(cases), all_expected_observations_matched=True,
                  cases=cases, limits=[
                      "No Odoo, database, HTTP endpoint or real user was exercised.",
                      "No official upgrade converter was executed.",
                      "The naive rewrite is an intentional counterexample, not converter output.",
                      "Groups are already-expanded fixture inputs; inheritance is not implemented.",
                      "No domain parser, field-access engine, sudo, caches or create/write hooks.",
                      "Company-dependent values and relation consistency are illustrative controls.",
                      "Native role/company/operation cases remain NOT_RUN.",
                  ])
    print(json.dumps(output, indent=2))


if __name__ == "__main__":
    main()
