import pytest

from iisi_app_core import (
    DomainValidationError,
    ForbiddenError,
    Principal,
    Role,
    authorize,
    ensure_at_least,
    require_role,
)


def test_principal_of_allows_empty_tenant_id() -> None:
    principal = Principal.of(name="reader@example.com", role=Role.READER, tenant_id="")
    assert principal.tenant_id == ""


def test_principal_of_rejects_non_string_tenant_id() -> None:
    with pytest.raises(DomainValidationError, match="tenant_id must be a string"):
        Principal.of(name="reader@example.com", role=Role.READER, tenant_id=None)  # type: ignore[arg-type]


def test_authorize_allows_higher_role_through_hierarchy() -> None:
    authorize(Principal.of(name="root", role=Role.ADMIN, tenant_id="tenant"), Role.READER)


def test_ensure_at_least_rejects_too_low_role() -> None:
    with pytest.raises(ForbiddenError):
        ensure_at_least(Principal.of(name="guest", role=Role.GUEST, tenant_id="tenant"), Role.READER)


def test_require_role_reads_principal_from_keyword_argument() -> None:
    @require_role(Role.READER)
    def handle(*, principal: Principal) -> str:
        return principal.name

    result = handle(principal=Principal.of(name="alice", role=Role.READER, tenant_id="tenant"))

    assert result == "alice"


def test_require_role_uses_custom_principal_getter() -> None:
    @require_role(Role.ADMIN, principal_getter=lambda args, kwargs: args[0].principal)
    def handle(command) -> str:  # noqa: ANN001, ANN202
        return command.principal.name

    class Command:
        def __init__(self, principal: Principal) -> None:
            self.principal = principal

    result = handle(Command(Principal.of(name="root", role=Role.ADMIN, tenant_id="tenant")))

    assert result == "root"


def test_require_role_raises_type_error_when_principal_is_missing() -> None:
    @require_role(Role.READER)
    def handle() -> str:
        return "ok"

    with pytest.raises(TypeError, match="principal"):
        handle()
