"""Authorization helpers."""

from collections.abc import Callable
from dataclasses import dataclass
from enum import Enum
from functools import wraps
from typing import ParamSpec, TypeVar

from .errors import DomainValidationError, ForbiddenError


class Role(str, Enum):
    GUEST = "GUEST"
    ADMIN = "ADMIN"
    READER = "READER"


@dataclass(slots=True, frozen=True)
class Principal:
    name: str
    role: Role
    tenant_id: str

    @classmethod
    def of(cls, name: str, role: Role, tenant_id: str) -> "Principal":
        if not isinstance(name, str) or not name.strip():
            raise DomainValidationError("Principal.name must be a non-empty string")
        if not isinstance(tenant_id, str):
            raise DomainValidationError("Principal.tenant_id must be a string")
        return cls(name=name.strip(), role=role, tenant_id=tenant_id.strip())

    @classmethod
    def guest(cls) -> "Principal":
        return cls(name="guest", role=Role.GUEST, tenant_id="guest")

    @classmethod
    def system(cls) -> "Principal":
        return cls(name="system", role=Role.ADMIN, tenant_id="system")

    def to_dict(self) -> dict[str, str]:
        return {
            "name": self.name,
            "role": self.role.value,
            "tenant_id": self.tenant_id,
        }


ROLE_ORDER = {
    Role.GUEST: 0,
    Role.READER: 1,
    Role.ADMIN: 2,
}

P = ParamSpec("P")
R = TypeVar("R")


def has_role(subject: Principal, role: Role) -> bool:
    return subject.role is role


def at_least(subject: Principal, minimum_role: Role) -> bool:
    return ROLE_ORDER[subject.role] >= ROLE_ORDER[minimum_role]


def authorize(subject: Principal, required_role: Role, *, use_hierarchy: bool = True) -> None:
    allowed = at_least(subject, required_role) if use_hierarchy else has_role(subject, required_role)
    if not allowed:
        raise ForbiddenError(f"Requires role {required_role.value}")


def ensure_at_least(subject: Principal, minimum_role: Role) -> None:
    authorize(subject, minimum_role, use_hierarchy=True)


def is_admin(subject: Principal) -> bool:
    return has_role(subject, Role.ADMIN)


def is_reader(subject: Principal) -> bool:
    return has_role(subject, Role.READER)


def require_role(required_role: Role, *, use_hierarchy: bool = True) -> Callable[[Callable[P, R]], Callable[P, R]]:
    """Guard a callable with the current principal."""

    def decorator(fn: Callable[P, R]) -> Callable[P, R]:
        @wraps(fn)
        def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
            from ..runtime.context import principal

            authorize(principal(), required_role, use_hierarchy=use_hierarchy)
            return fn(*args, **kwargs)

        return wrapper

    return decorator


__all__ = [
    "ForbiddenError",
    "Principal",
    "ROLE_ORDER",
    "Role",
    "at_least",
    "authorize",
    "ensure_at_least",
    "has_role",
    "is_admin",
    "is_reader",
    "require_role",
]
