# #region Test.SupersetProfileLookup [C:3] [TYPE Module] [SEMANTICS test, superset, profile, lookup, adapter]
# @BRIEF Tests for core/superset_profile_lookup.py — SupersetAccountLookupAdapter.
# @RELATION BINDS_TO -> [SupersetProfileLookup]

from pathlib import Path
import sys

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

from unittest.mock import AsyncMock, MagicMock, patch

import pytest


class TestSupersetAccountLookupAdapterInit:
    """__init__: stores client and environment ID."""

    def test_init(self):
        from src.core.superset_profile_lookup import (
            SupersetAccountLookupAdapter,
        )

        client = MagicMock()
        adapter = SupersetAccountLookupAdapter(client, "env-1")
        assert adapter.network_client is client
        assert adapter.environment_id == "env-1"

    def test_init_converts_environment_id_to_string(self):
        from src.core.superset_profile_lookup import (
            SupersetAccountLookupAdapter,
        )

        adapter = SupersetAccountLookupAdapter(MagicMock(), 123)
        assert adapter.environment_id == "123"


class TestGetUsersPage:
    """get_users_page: fetch and normalize users."""

    @pytest.fixture
    def adapter(self):
        from src.core.superset_profile_lookup import (
            SupersetAccountLookupAdapter,
        )

        client = MagicMock()
        client.request = AsyncMock()
        return SupersetAccountLookupAdapter(client, "env-1")

    @pytest.mark.asyncio
    async def test_successful_first_endpoint(self, adapter):
        adapter.network_client.request.return_value = {
            "result": [
                {"username": "alice", "email": "alice@example.com"},
                {"username": "bob", "email": "bob@example.com"},
            ],
            "count": 2,
        }

        result = await adapter.get_users_page(search="alice")
        assert result["status"] == "success"
        assert result["environment_id"] == "env-1"
        assert len(result["items"]) == 2
        assert result["total"] == 2

    @pytest.mark.asyncio
    async def test_first_endpoint_fails_fallback_succeeds(self, adapter):
        adapter.network_client.request = AsyncMock(
            side_effect=[
                Exception("First endpoint failed"),
                {
                    "result": [
                        {"username": "charlie", "email": "charlie@example.com"}
                    ],
                    "count": 1,
                },
            ]
        )

        result = await adapter.get_users_page()
        assert len(result["items"]) == 1
        assert result["items"][0]["username"] == "charlie"

    @pytest.mark.asyncio
    async def test_both_endpoints_fail_raises_last(self, adapter):
        adapter.network_client.request = AsyncMock(
            side_effect=[
                Exception("First failed"),
                Exception("Second failed"),
            ]
        )

        with pytest.raises(Exception, match="Second failed"):
            await adapter.get_users_page()

    @pytest.mark.asyncio
    async def test_prefers_primary_error_over_auth_fallback(self, adapter):
        adapter.network_client.request = AsyncMock(
            side_effect=[
                ValueError("Primary error"),
                __import__("src").core.utils.network.AuthenticationError("Auth error"),
            ]
        )

        with pytest.raises(ValueError, match="Primary error"):
            await adapter.get_users_page()

    @pytest.mark.asyncio
    async def test_preserves_primary_non_auth_over_fallback_auth(self, adapter):
        adapter.network_client.request = AsyncMock(
            side_effect=[
                RuntimeError("Primary runtime error"),
                __import__("src").core.utils.network.AuthenticationError("Fallback auth error"),
            ]
        )

        with pytest.raises(RuntimeError, match="Primary runtime error"):
            await adapter.get_users_page()

    @pytest.mark.asyncio
    async def test_normalizes_page_and_size_params(self, adapter):
        adapter.network_client.request.return_value = {"result": [], "count": 0}

        result = await adapter.get_users_page(page_index=-1, page_size=0)
        assert result["page_index"] == 0
        assert result["page_size"] == 1

    @pytest.mark.asyncio
    async def test_invalid_sort_order_defaults_to_desc(self, adapter):
        adapter.network_client.request.return_value = {"result": [], "count": 0}

        await adapter.get_users_page(sort_order="invalid")
        # Should have used "desc"
        call_q = adapter.network_client.request.call_args[1]["params"]["q"]
        import json

        q = json.loads(call_q)
        assert q["order_direction"] == "desc"

    @pytest.mark.asyncio
    async def test_empty_search_no_filters(self, adapter):
        adapter.network_client.request.return_value = {"result": [], "count": 0}

        await adapter.get_users_page(search="")
        call_q = adapter.network_client.request.call_args[1]["params"]["q"]
        import json

        q = json.loads(call_q)
        assert "filters" not in q

    @pytest.mark.asyncio
    async def test_no_exception_but_also_no_last_error(self, adapter):
        # Both endpoints return data, no exception at all
        adapter.network_client.request.return_value = {"result": [], "count": 0}
        result = await adapter.get_users_page()
        assert result["status"] == "success"


class TestNormalizeLookupPayload:
    """_normalize_lookup_payload: various response shapes."""

    @pytest.fixture
    def adapter(self):
        from src.core.superset_profile_lookup import (
            SupersetAccountLookupAdapter,
        )

        return SupersetAccountLookupAdapter(MagicMock(), "env-1")

    def test_result_dict_with_result_list(self, adapter):
        result = adapter._normalize_lookup_payload(
            {"result": [{"username": "alice"}], "count": 1}, 0, 20
        )
        assert len(result["items"]) == 1
        assert result["total"] == 1

    def test_result_dict_with_users_list(self, adapter):
        result = adapter._normalize_lookup_payload(
            {"users": [{"username": "bob"}], "total": 1}, 0, 20
        )
        assert len(result["items"]) == 1

    def test_result_dict_with_items_list(self, adapter):
        result = adapter._normalize_lookup_payload(
            {"items": [{"username": "charlie"}], "total": 1}, 0, 20
        )
        assert len(result["items"]) == 1

    def test_result_list_direct(self, adapter):
        result = adapter._normalize_lookup_payload(
            [{"username": "dave"}, {"username": "eve"}], 0, 20
        )
        assert len(result["items"]) == 2
        assert result["total"] == 2

    def test_double_nested_result(self, adapter):
        result = adapter._normalize_lookup_payload(
            {"result": {"result": [{"username": "frank"}]}}, 0, 20
        )
        assert len(result["items"]) == 1

    def test_skips_duplicate_usernames(self, adapter):
        result = adapter._normalize_lookup_payload(
            {
                "result": [
                    {"username": "alice"},
                    {"username": "ALICE"},
                    {"username": "bob"},
                ],
                "count": 3,
            },
            0,
            20,
        )
        # "alice" and "ALICE" normalized to same lowercase
        assert len(result["items"]) == 2

    def test_skips_empty_username(self, adapter):
        result = adapter._normalize_lookup_payload(
            {"result": [{"username": ""}, {"username": "valid"}], "count": 2}, 0, 20
        )
        assert len(result["items"]) == 1

    def test_total_adjusted_to_max(self, adapter):
        result = adapter._normalize_lookup_payload(
            {"result": [{"username": "a"}, {"username": "b"}], "count": 1}, 0, 20
        )
        # total should be max(1, 2) = 2
        assert result["total"] == 2

    def test_non_dict_non_list_response(self, adapter):
        result = adapter._normalize_lookup_payload("invalid", 0, 20)
        assert result["items"] == []
        assert result["total"] == 0


class TestNormalizeUserPayload:
    """normalize_user_payload: canonical user shape."""

    @pytest.fixture
    def adapter(self):
        from src.core.superset_profile_lookup import (
            SupersetAccountLookupAdapter,
        )

        return SupersetAccountLookupAdapter(MagicMock(), "env-1")

    def test_full_user(self, adapter):
        user = adapter.normalize_user_payload({
            "username": "alice",
            "full_name": "Alice Smith",
            "email": "alice@example.com",
            "is_active": True,
        })
        assert user["username"] == "alice"
        assert user["display_name"] == "Alice Smith"
        assert user["email"] == "alice@example.com"
        assert user["is_active"] is True
        assert user["environment_id"] == "env-1"

    def test_with_first_last_name(self, adapter):
        user = adapter.normalize_user_payload({
            "username": "bob",
            "first_name": "Bob",
            "last_name": "Jones",
        })
        assert user["display_name"] == "Bob Jones"

    def test_empty_display_name_falls_back_to_username(self, adapter):
        user = adapter.normalize_user_payload({
            "username": "charlie",
        })
        assert user["display_name"] == "charlie"

    def test_email_empty_becomes_none(self, adapter):
        user = adapter.normalize_user_payload({
            "username": "dave",
            "email": "",
        })
        assert user["email"] is None

    def test_is_active_none(self, adapter):
        user = adapter.normalize_user_payload({
            "username": "eve",
        })
        assert user["is_active"] is None

    def test_alternate_key_names(self, adapter):
        user = adapter.normalize_user_payload({
            "userName": "frank",
            "name": "ignored",
        })
        assert user["username"] == "frank"

    def test_non_dict_input(self, adapter):
        user = adapter.normalize_user_payload("not_a_dict")
        assert user["username"] == ""
# #endregion Test.SupersetProfileLookup
