from __future__ import annotations

import pytest
from punq import Container

from iisi_app_core import (
    FeatureDisabledError,
    clear_default_container,
    clear_feature_flag_config,
    configure_feature_flags,
    feature_enabled,
    set_default_container,
)


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 setup_function() -> None:
    clear_default_container()
    clear_feature_flag_config()


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

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

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


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

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

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


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

    class RegistrationDisabled(Exception):
        pass

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

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


def test_feature_enabled_raises_clear_error_when_not_configured() -> None:
    with pytest.raises(RuntimeError):

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


def test_feature_enabled_raises_clear_error_when_settings_binding_missing() -> None:
    container = Container()
    set_default_container(container)
    configure_feature_flags(SettingsPort)

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

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


def test_feature_enabled_rejects_invalid_feature_path() -> None:
    configure_feature_flags(SettingsPort)

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


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

    calls: list[str] = []

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

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

    assert calls == ["/feature/auth/register_user/enabled"]
    assert UseCase()() == "ok"
    assert UseCase()() == "ok"
    assert calls == ["/feature/auth/register_user/enabled"]
