from base64 import b64encode
from dataclasses import dataclass
from datetime import timezone
from io import StringIO

import pytest

from iisi_app_core import (
    ForbiddenError,
    LoggingConfig,
    Principal,
    RequestScope,
    Role,
    asdict_primitives,
    configure_logging,
    ensure_at_least,
    ensure_utc_datetime,
    get_logger,
    isoformat_z,
    make_log_hook,
    require_role,
)


@dataclass
class Sample:
    name: str
    amount: int


def test_datetime_helpers_roundtrip_utc() -> None:
    dt = ensure_utc_datetime("2026-03-05T10:00:00+02:00")
    assert dt.tzinfo == timezone.utc
    assert isoformat_z(dt).endswith("Z")


def test_asdict_primitives_serializes_dataclass() -> None:
    data = asdict_primitives(Sample(name="x", amount=1))
    assert data == {"name": "x", "amount": 1}


def test_asdict_primitives_serializes_bytes_to_base64() -> None:
    assert asdict_primitives({"value": b"abc"}) == {"value": b64encode(b"abc").decode("ascii")}


def test_require_role_enforces_hierarchy() -> None:
    with RequestScope(principal=Principal.of(name="alice", role=Role.READER, tenant_id="tenant")):

        @require_role(Role.READER)
        def allowed() -> str:
            return "ok"

        @require_role(Role.ADMIN)
        def denied() -> str:
            return "no"

        assert allowed() == "ok"
        with pytest.raises(ForbiddenError):
            denied()


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


def test_get_logger_returns_logger() -> None:
    stream = StringIO()
    configure_logging(LoggingConfig(logger_names=("iisi_app_core.tests",), stream=stream))
    logger = get_logger("iisi_app_core.tests")
    assert logger.name == "iisi_app_core.tests"
    assert len(logger.handlers) == 1


def test_configure_logging_is_idempotent() -> None:
    stream = StringIO()
    config = LoggingConfig(logger_names=("iisi_app_core.idempotent",), stream=stream)

    configure_logging(config)
    configure_logging(config)

    logger = get_logger("iisi_app_core.idempotent")

    assert len(logger.handlers) == 1


def test_make_log_hook_masks_sensitive_values() -> None:
    stream = StringIO()
    configure_logging(LoggingConfig(logger_names=("iisi_app_core.hook",), stream=stream))
    hook = make_log_hook(get_logger("iisi_app_core.hook"))

    class Request:
        method = "GET"
        url = "https://example.com/items?token=secret"
        headers = {"Authorization": "Bearer abc"}
        body = None

    class Response:
        request = Request()
        headers = {"Authorization": "Bearer abc"}
        text = ""
        status_code = 200

    hook(Response())

    output = stream.getvalue()
    assert '"Authorization": "*****"' in output
    assert "token=%2A%2A%2A%2A%2A" in output
