"""Ownership contract validation for effective Cloud entity schemas (Phase 11D8B).

Create permission, ownership assignment, and owner-scoped authorization are
three different concerns (11D8A). Cloud stamps ``owner_user_id`` only when
``rls_enabled`` or ``owner_managed`` is set. ``schema.access_policy`` never
activates that stamp.

This module rejects the contradiction where a creating app-user role is also
expected to retain owner-scoped read/update/delete, but neither stamp flag is
on. It does not infer that every ``scope=owner`` policy means creator ownership,
and it does not change ``resolveCreateOwner``.
"""
from __future__ import annotations

from typing import Any

INVALID_OWNERSHIP_CONTRACT = "INVALID_OWNERSHIP_CONTRACT"

_LATER_ACTIONS = ("read", "update", "delete")


class OwnershipContractError(ValueError):
    """Deterministic composition/install failure for a contradictory owner contract."""

    def __init__(
        self,
        message: str,
        *,
        entity_key: str,
        role: str,
        actions: tuple[str, ...],
        rls_enabled: bool,
        owner_managed: bool,
    ) -> None:
        super().__init__(message)
        self.code = INVALID_OWNERSHIP_CONTRACT
        self.entity_key = entity_key
        self.role = role
        self.actions = actions
        self.rls_enabled = rls_enabled
        self.owner_managed = owner_managed
        self.ownership_requirement = "create_owner_stamp"

    def as_blocker(self) -> dict[str, Any]:
        return {
            "errors": [str(self)],
            "validation_code": self.code,
            "entity_key": self.entity_key,
            "role": self.role,
            "ownership_requirement": self.ownership_requirement,
        }


def _truthy_flag(entity: dict[str, Any], name: str) -> bool:
    if entity.get(name) is True:
        return True
    settings = entity.get("settings")
    return isinstance(settings, dict) and settings.get(name) is True


def _create_owner_stamp_enabled(entity: dict[str, Any]) -> bool:
    return _truthy_flag(entity, "rls_enabled") or _truthy_flag(entity, "owner_managed")


def _is_allow(entry: Any) -> bool:
    return isinstance(entry, dict) and entry.get("effect") == "allow"


def _later_scope(entry: dict[str, Any]) -> str:
    # Match Cloud checkAccessPolicy: omitted allow-scope defaults to owner.
    scope = entry.get("scope")
    return scope if isinstance(scope, str) and scope else "owner"


def validate_entity_ownership_contract(entity: dict[str, Any]) -> None:
    """Validate one effective entity dict as Cloud create/install would receive it."""
    if not isinstance(entity, dict):
        return
    schema = entity.get("schema")
    if not isinstance(schema, dict):
        return
    policy = schema.get("access_policy")
    if not isinstance(policy, dict):
        return
    roles = policy.get("roles")
    if not isinstance(roles, dict):
        return

    stamp_enabled = _create_owner_stamp_enabled(entity)
    entity_key = str(entity.get("entity_key") or entity.get("key") or "")
    rls_enabled = _truthy_flag(entity, "rls_enabled")
    owner_managed = _truthy_flag(entity, "owner_managed")

    for role, actions in roles.items():
        if not isinstance(actions, dict):
            continue
        if not _is_allow(actions.get("create")):
            continue
        owner_later = tuple(
            action
            for action in _LATER_ACTIONS
            if _is_allow(actions.get(action)) and _later_scope(actions[action]) == "owner"
        )
        if not owner_later or stamp_enabled:
            continue
        role_name = str(role)
        key_label = entity_key or "<unknown>"
        action_list = ", ".join(owner_later)
        raise OwnershipContractError(
            f"{INVALID_OWNERSHIP_CONTRACT}: Owner-scoped {role_name} access requires "
            "an ownership creation contract, but this entity allows "
            f"{role_name} creation without an active ownership mechanism "
            f"(rls_enabled or owner_managed). entity_key={key_label} "
            f"role={role_name} scope=owner actions={action_list} "
            f"rls_enabled={str(rls_enabled).lower()} "
            f"owner_managed={str(owner_managed).lower()}",
            entity_key=key_label,
            role=role_name,
            actions=owner_later,
            rls_enabled=rls_enabled,
            owner_managed=owner_managed,
        )


def validate_composition_ownership_contracts(composition: dict[str, Any] | None) -> None:
    """Validate every entity on a canonical/effective composition blob."""
    if not isinstance(composition, dict):
        return
    for entity in composition.get("entities") or []:
        if isinstance(entity, dict):
            validate_entity_ownership_contract(entity)
