from __future__ import annotations

from contextlib import asynccontextmanager
from unittest.mock import AsyncMock, patch

import pytest

from sibyl.persistence.settings_types import SystemSettingRecord
from sibyl.services.settings import SettingsService


@pytest.mark.asyncio
async def test_settings_service_get_uses_runtime_helper_and_cache() -> None:
    @asynccontextmanager
    async def mock_session():
        yield None

    service = SettingsService(mock_session)
    setting = SystemSettingRecord(key="openai_api_key", value="ciphertext", is_secret=True)

    with (
        patch(
            "sibyl.services.settings.get_system_setting",
            AsyncMock(return_value=setting),
        ) as get_setting,
        patch("sibyl.services.settings.decrypt_value", return_value="sk-openai"),
    ):
        first = await service.get("openai_api_key")
        second = await service.get("openai_api_key")

    assert first == "sk-openai"
    assert second == "sk-openai"
    get_setting.assert_awaited_once_with(None, key="openai_api_key")


@pytest.mark.asyncio
async def test_settings_service_set_uses_runtime_save_and_invalidates_cache() -> None:
    @asynccontextmanager
    async def mock_session():
        yield None

    service = SettingsService(mock_session)
    service._cache["openai_api_key"] = object()  # type: ignore[assignment]

    with (
        patch(
            "sibyl.services.settings.get_system_setting",
            AsyncMock(return_value=None),
        ) as get_setting,
        patch(
            "sibyl.services.settings.save_system_setting",
            AsyncMock(),
        ) as save_setting,
        patch("sibyl.services.settings.encrypt_value", return_value="ciphertext"),
    ):
        await service.set(
            "openai_api_key",
            "sk-openai",
            description="OpenAI key",
        )

    get_setting.assert_awaited_once_with(None, key="openai_api_key")
    save_setting.assert_awaited_once()
    saved = save_setting.await_args.kwargs["setting"]
    assert isinstance(saved, SystemSettingRecord)
    assert saved.key == "openai_api_key"
    assert saved.value == "ciphertext"
    assert saved.is_secret is True
    assert saved.description == "OpenAI key"
    assert "openai_api_key" not in service._cache


@pytest.mark.asyncio
async def test_settings_service_get_all_uses_runtime_listing() -> None:
    @asynccontextmanager
    async def mock_session():
        yield None

    service = SettingsService(mock_session)
    settings = [
        SystemSettingRecord(key="openai_api_key", value="ciphertext", is_secret=True),
        SystemSettingRecord(key="feature_flag", value="enabled", is_secret=False),
    ]

    with (
        patch(
            "sibyl.services.settings.list_system_settings",
            AsyncMock(return_value=settings),
        ) as list_settings,
        patch("sibyl.services.settings.decrypt_value", return_value="sk-openai"),
        patch("sibyl.services.settings.mask_secret", return_value="sk-***"),
    ):
        result = await service.get_all(include_secrets=False)

    list_settings.assert_awaited_once_with(None)
    assert result["openai_api_key"] == {
        "configured": True,
        "source": "database",
        "is_secret": True,
        "value": None,
        "masked": "sk-***",
        "description": None,
    }
    assert result["feature_flag"] == {
        "configured": True,
        "source": "database",
        "is_secret": False,
        "value": "enabled",
        "masked": None,
        "description": None,
    }


@pytest.mark.asyncio
async def test_settings_service_llm_settings_use_persisted_keys() -> None:
    @asynccontextmanager
    async def mock_session():
        yield None

    service = SettingsService(mock_session)

    with (
        patch(
            "sibyl.services.settings.get_system_setting",
            AsyncMock(return_value=None),
        ),
        patch(
            "sibyl.services.settings.save_system_setting",
            AsyncMock(),
        ) as save_setting,
    ):
        await service.set_llm_setting("crawler", "model", "gemini-3-flash")

    saved = save_setting.await_args.kwargs["setting"]
    assert saved.key == "llm.crawler.model"
    assert saved.value == "gemini-3-flash"
    assert saved.is_secret is False


@pytest.mark.asyncio
async def test_settings_service_get_database_value_skips_env_fallback(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    @asynccontextmanager
    async def mock_session():
        yield None

    service = SettingsService(mock_session)
    monkeypatch.setenv("SIBYL_LLM_CRAWLER_MODEL", "env-model")

    with patch(
        "sibyl.services.settings.get_system_setting",
        AsyncMock(return_value=None),
    ):
        value = await service.get_database_value("llm.crawler.model")

    assert value is None
