"""Panel-tunable feature flags — validation, bool read, and startup overlay (#9).

Flags persist in system_settings as 'true'/'false' and are overlaid onto the
config singleton at startup; an unset flag keeps its env/config default.
"""

from __future__ import annotations

from unittest.mock import AsyncMock, MagicMock

import pytest
from roboco.config import settings as cfg
from roboco.services import settings as settings_mod
from roboco.services.settings import (
    SettingsService,
    SettingValidationError,
    validate_setting,
)


def test_feature_flags_are_writable_as_bool() -> None:
    validate_setting("external_pr_enabled", "true")
    validate_setting("research_enabled", "FALSE")  # case-insensitive
    validate_setting("internal_pr_enabled", "  true  ")  # whitespace tolerated


def test_non_bool_value_rejected() -> None:
    with pytest.raises(SettingValidationError):
        validate_setting("external_pr_enabled", "yes")


def test_unknown_key_rejected() -> None:
    with pytest.raises(SettingValidationError):
        validate_setting("not_a_real_flag", "true")


@pytest.mark.asyncio
async def test_get_bool_parses_and_defaults() -> None:
    svc = SettingsService(MagicMock())
    object.__setattr__(svc, "get", AsyncMock(return_value="true"))
    assert await svc.get_bool("k", default=False) is True
    object.__setattr__(svc, "get", AsyncMock(return_value="false"))
    assert await svc.get_bool("k", default=True) is False
    object.__setattr__(svc, "get", AsyncMock(return_value=None))
    assert await svc.get_bool("k", default=True) is True  # unset → default


@pytest.mark.asyncio
async def test_apply_overrides_stored_flags_only(
    monkeypatch: pytest.MonkeyPatch,
) -> None:

    # Baseline env defaults.
    monkeypatch.setattr(cfg, "external_pr_enabled", False)
    monkeypatch.setattr(cfg, "research_enabled", True)

    # Only external_pr_enabled has a stored override; research_enabled is unset.
    stored = {"external_pr_enabled": "true"}

    async def fake_get(_self: SettingsService, key: str) -> str | None:
        return stored.get(key)

    monkeypatch.setattr(SettingsService, "get", fake_get)
    applied = await settings_mod.apply_persisted_feature_flags(MagicMock())

    assert "external_pr_enabled" in applied
    assert cfg.external_pr_enabled is True  # stored override applied
    assert cfg.research_enabled is True  # unset → env default untouched
    assert "research_enabled" not in applied


@pytest.mark.asyncio
async def test_effective_values_use_env_default_when_unset(
    monkeypatch: pytest.MonkeyPatch,
) -> None:

    monkeypatch.setattr(cfg, "strategy_engine_enabled", True)

    async def fake_get(_self: SettingsService, _key: str) -> str | None:
        return None  # nothing stored

    monkeypatch.setattr(SettingsService, "get", fake_get)
    effective = await settings_mod.feature_flag_effective_values(MagicMock())
    assert effective["strategy_engine_enabled"] is True  # falls back to env
