# #region Test.SupersetLookupService [C:3] [TYPE Module] [SEMANTICS test,superset,lookup]
# @BRIEF Verify SupersetLookupService — environment resolution, success lookup, degradation fallback, sort normalization.
# @RELATION BINDS_TO -> [SupersetLookupService]
# @TEST_EDGE: environment_not_found -> raises EnvironmentNotFoundError
# @TEST_EDGE: external_fail -> returns degraded response with warning
# @TEST_EDGE: invalid_sort_column -> falls back to "username"
# @TEST_EDGE: invalid_sort_order -> falls back to "desc"

from pathlib import Path
import sys

sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

from unittest.mock import AsyncMock, MagicMock, patch
import pytest

from src.schemas.profile import SupersetAccountLookupRequest


class _Env:
    def __init__(self, env_id):
        self.id = env_id


def _make_request(**overrides):
    defaults = dict(
        environment_id="env-1", search=None, page_index=0,
        page_size=20, sort_column="username", sort_order="desc",
    )
    defaults.update(overrides)
    req = MagicMock(spec=SupersetAccountLookupRequest)
    for k, v in defaults.items():
        setattr(req, k, v)
    return req


def _wire_client(mock_client_cls):
    client = MagicMock()
    client.client = MagicMock()
    mock_client_cls.return_value = client


class TestSupersetLookupService:
    """SupersetLookupService — environment-scoped Superset account lookup."""

    @pytest.fixture
    def config_manager(self):
        cm = MagicMock()
        cm.get_environments.return_value = [_Env("env-1"), _Env("env-2")]
        return cm

    @pytest.fixture
    def service(self, config_manager):
        from src.services.superset_lookup_service import SupersetLookupService
        return SupersetLookupService(config_manager)

    # #region test_init_stores_config [C:2] [TYPE Function]
    # @BRIEF Constructor stores config_manager reference.
    def test_init_stores_config(self, config_manager):
        from src.services.superset_lookup_service import SupersetLookupService
        svc = SupersetLookupService(config_manager)
        assert svc.config_manager is config_manager
    # #endregion test_init_stores_config

    # #region test_resolve_env_found [C:2] [TYPE Function]
    # @BRIEF Returns environment when id matches.
    def test_resolve_env_found(self, service):
        env = service._resolve_environment("env-1")
        assert env is not None and env.id == "env-1"
    # #endregion test_resolve_env_found

    # #region test_resolve_env_not_found [C:2] [TYPE Function]
    # @BRIEF Returns None for unknown environment id.
    def test_resolve_env_not_found(self, service):
        assert service._resolve_environment("non-existent") is None
    # #endregion test_resolve_env_not_found

    # #region test_lookup_env_not_found_raises [C:2] [TYPE Function]
    # @BRIEF Raises EnvironmentNotFoundError when environment_id is unknown.
    @pytest.mark.asyncio
    async def test_lookup_env_not_found_raises(self, service):
        from src.services.superset_lookup_service import EnvironmentNotFoundError
        with pytest.raises(EnvironmentNotFoundError, match="non-existent"):
            await service.lookup_superset_accounts(MagicMock(), _make_request(environment_id="non-existent"))
    # #endregion test_lookup_env_not_found_raises

    # #region test_lookup_success [C:2] [TYPE Function]
    # @BRIEF Happy path: adapter returns items → success response with candidates.
    @pytest.mark.asyncio
    async def test_lookup_success(self, service):
        adapter = AsyncMock()
        adapter.get_users_page.return_value = {
            "items": [{"environment_id": "env-1", "username": "admin", "display_name": "Admin", "email": "a@x.com"}],
            "total": 1,
        }
        with patch("src.services.superset_lookup_service.AsyncSupersetClient") as mc, \
             patch("src.services.superset_lookup_service.SupersetAccountLookupAdapter", return_value=adapter):
            _wire_client(mc)
            resp = await service.lookup_superset_accounts(MagicMock(id="u1"), _make_request())

        assert resp.status == "success"
        assert resp.environment_id == "env-1"
        assert resp.total == 1
        assert len(resp.items) == 1
        assert resp.items[0].username == "admin"
        assert resp.warning is None
    # #endregion test_lookup_success

    # #region test_lookup_degraded_on_exception [C:2] [TYPE Function]
    # @BRIEF External failure → degraded response with warning, empty items.
    @pytest.mark.asyncio
    async def test_lookup_degraded_on_exception(self, service):
        with patch("src.services.superset_lookup_service.AsyncSupersetClient", side_effect=ConnectionError("down")):
            resp = await service.lookup_superset_accounts(MagicMock(id="u1"), _make_request())

        assert resp.status == "degraded"
        assert resp.total == 0
        assert resp.items == []
        assert "Cannot load" in resp.warning
    # #endregion test_lookup_degraded_on_exception

    # #region test_sort_normalization [C:2] [TYPE Function]
    # @BRIEF Invalid sort_column → "username"; invalid sort_order → "desc"; None → defaults.
    @pytest.mark.asyncio
    async def test_sort_normalization(self, service):
        adapter = AsyncMock()
        adapter.get_users_page.return_value = {"items": [], "total": 0}

        cases = [
            ({"sort_column": "invalid_col", "sort_order": "desc"}, "username", "desc"),
            ({"sort_column": "username", "sort_order": "invalid"}, "username", "desc"),
            ({"sort_column": None, "sort_order": None}, "username", "desc"),
        ]
        for overrides, expected_col, expected_order in cases:
            adapter.reset_mock()
            with patch("src.services.superset_lookup_service.AsyncSupersetClient") as mc, \
                 patch("src.services.superset_lookup_service.SupersetAccountLookupAdapter", return_value=adapter):
                _wire_client(mc)
                await service.lookup_superset_accounts(MagicMock(id="u1"), _make_request(**overrides))

            kw = adapter.get_users_page.call_args.kwargs
            assert kw["sort_column"] == expected_col, f"Failed for overrides={overrides}"
            assert kw["sort_order"] == expected_order, f"Failed for overrides={overrides}"
    # #endregion test_sort_normalization

    # #region test_success_total_fallback_to_items_len [C:2] [TYPE Function]
    # @BRIEF When adapter omits 'total', response total falls back to len(items).
    @pytest.mark.asyncio
    async def test_success_total_fallback_to_items_len(self, service):
        adapter = AsyncMock()
        adapter.get_users_page.return_value = {
            "items": [
                {"environment_id": "env-1", "username": "a"},
                {"environment_id": "env-1", "username": "b"},
            ],
        }
        with patch("src.services.superset_lookup_service.AsyncSupersetClient") as mc, \
             patch("src.services.superset_lookup_service.SupersetAccountLookupAdapter", return_value=adapter):
            _wire_client(mc)
            resp = await service.lookup_superset_accounts(MagicMock(id="u1"), _make_request())

        assert resp.status == "success"
        assert resp.total == 2
    # #endregion test_success_total_fallback_to_items_len
# #endregion Test.SupersetLookupService
