import pytest
from punq import Container

from iisi_app_core import FeatureDisabledError, MissingRequestScopeError, RequestScope, feature_enabled


class SettingsPort:
    def is_feature_enabled(self, name, default: bool = False) -> bool:
        raise NotImplementedError


class FakeSettings(SettingsPort):
    def __init__(self, enabled: bool) -> None:
        self.enabled = enabled
        self.calls: list[tuple[object, bool]] = []

    def is_feature_enabled(self, name, default: bool = False) -> bool:
        self.calls.append((name, default))
        return self.enabled


def test_feature_enabled_allows_execution_when_feature_is_enabled() -> None:
    container = Container()
    settings = FakeSettings(enabled=True)
    container.register(SettingsPort, instance=settings)

    @feature_enabled("/feature/auth/register_user", settings_interface=SettingsPort)
    class UseCase:
        def __call__(self) -> str:
            return "ok"

    with RequestScope(container=container):
        assert UseCase()() == "ok"

    assert settings.calls == [("/feature/auth/register_user/enabled", True)]


def test_feature_enabled_raises_default_exception_when_disabled() -> None:
    container = Container()
    container.register(SettingsPort, instance=FakeSettings(enabled=False))

    @feature_enabled("/feature/auth/register_user", settings_interface=SettingsPort)
    class UseCase:
        def __call__(self) -> str:
            return "ok"

    with RequestScope(container=container):
        with pytest.raises(FeatureDisabledError):
            UseCase()()


def test_feature_enabled_supports_custom_disabled_exception_override() -> None:
    container = Container()
    container.register(SettingsPort, instance=FakeSettings(enabled=False))

    class RegistrationDisabled(Exception):
        pass

    @feature_enabled(
        "/feature/auth/register_user",
        settings_interface=SettingsPort,
        disabled_exception=RegistrationDisabled,
    )
    class UseCase:
        def __call__(self) -> str:
            return "ok"

    with RequestScope(container=container):
        with pytest.raises(RegistrationDisabled):
            UseCase()()


def test_feature_enabled_requires_request_scope_when_container_not_passed() -> None:
    @feature_enabled("/feature/auth/register_user", settings_interface=SettingsPort)
    class UseCase:
        def __call__(self) -> str:
            return "ok"

    with pytest.raises(MissingRequestScopeError):
        UseCase()()


def test_feature_enabled_raises_clear_error_when_settings_binding_missing() -> None:
    container = Container()

    @feature_enabled("/feature/auth/register_user", settings_interface=SettingsPort)
    class UseCase:
        def __call__(self) -> str:
            return "ok"

    with RequestScope(container=container):
        with pytest.raises(RuntimeError, match="Could not resolve feature settings interface"):
            UseCase()()


def test_feature_enabled_rejects_invalid_feature_path() -> None:
    with pytest.raises(ValueError, match="non-empty"):
        feature_enabled("", settings_interface=SettingsPort)
    with pytest.raises(ValueError, match="starting with /feature/"):
        feature_enabled("/notfeature/auth/register_user", settings_interface=SettingsPort)
    with pytest.raises(ValueError, match="/feature/<context>/<use_case>"):
        feature_enabled("/feature/auth/register_user/extra", settings_interface=SettingsPort)


def test_setting_name_factory_is_computed_once_at_decoration_time() -> None:
    container = Container()
    settings = FakeSettings(enabled=True)
    container.register(SettingsPort, instance=settings)
    calls: list[str] = []

    def factory(name: str) -> str:
        calls.append(name)
        return f"NAME::{name}"

    @feature_enabled("/feature/auth/register_user", settings_interface=SettingsPort, setting_name_factory=factory)
    class UseCase:
        def __call__(self) -> str:
            return "ok"

    with RequestScope(container=container):
        assert UseCase()() == "ok"
        assert UseCase()() == "ok"

    assert calls == ["/feature/auth/register_user/enabled"]
    assert settings.calls == [("NAME::/feature/auth/register_user/enabled", True)] * 2
