# #region Test.AsyncNetwork.Edge [C:4] [TYPE Module] [SEMANTICS test, async, network, auth, upload, coverage]
# @BRIEF Additional edge case tests for AsyncAPIClient — cache hit after lock, upload status, and remaining branches.
# @RELATION BINDS_TO -> [AsyncNetworkModule]

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

import asyncio
import json
from unittest.mock import AsyncMock, MagicMock, patch

import httpx
import pytest


@pytest.fixture(autouse=True)
def clear_auth_cache():
    from src.core.utils.network import SupersetAuthCache
    SupersetAuthCache._entries.clear()


class TestAuthenticateEdge:
    @pytest.mark.asyncio
    async def test_csrf_refresh_failure_does_not_block(self):
        """Cache hit, CSRF refresh fails → cached tokens still returned."""
        from src.core.utils.async_network import AsyncAPIClient
        from src.core.utils.network import SupersetAuthCache

        client = AsyncAPIClient({"base_url": "https://test.com", "auth": {}})
        client._client = AsyncMock()
        SupersetAuthCache.set(
            client._auth_cache_key,
            {"access_token": "cached", "csrf_token": "cached_csrf"},
        )
        # CSRF refresh raises exception
        client._client.get = AsyncMock(side_effect=httpx.ConnectError("network error"))

        tokens = await client.authenticate()
        assert tokens["access_token"] == "cached"

    @pytest.mark.asyncio
    async def test_cache_hit_after_wait_no_refresh(self):
        """Cache hit after wait with failed CSRF refresh returns cached tokens."""
        from src.core.utils.async_network import AsyncAPIClient
        from src.core.utils.network import SupersetAuthCache

        client = AsyncAPIClient({"base_url": "https://test.com", "auth": {}})
        client._client = AsyncMock()
        # Put in cache so the lock path finds it
        SupersetAuthCache.set(
            client._auth_cache_key,
            {"access_token": "after_wait", "csrf_token": "after_csrf"},
        )
        # Make CSRF get fail
        client._client.get = AsyncMock(side_effect=Exception("CSRF fail"))

        tokens = await client.authenticate()
        assert tokens["access_token"] == "after_wait"

    @pytest.mark.asyncio
    async def test_cache_miss_key_error(self):
        """login response missing access_token key → NetworkError."""
        from src.core.utils.async_network import AsyncAPIClient
        from src.core.utils.network import NetworkError

        client = AsyncAPIClient({"base_url": "https://test.com", "auth": {}})
        client._client = AsyncMock()
        mock_resp = MagicMock()
        mock_resp.status_code = 200
        mock_resp.json.return_value = {"not_access_token": "val"}
        mock_resp.raise_for_status = MagicMock()
        client._client.post = AsyncMock(return_value=mock_resp)

        with pytest.raises(NetworkError, match="Network or parsing error"):
            await client.authenticate()

    @pytest.mark.asyncio
    async def test_authenticate_httpx_http_error_other(self):
        """Non-5xx HTTP error → AuthenticationError."""
        from src.core.utils.async_network import AsyncAPIClient
        from src.core.utils.network import AuthenticationError

        client = AsyncAPIClient({"base_url": "https://test.com", "auth": {}})
        client._client = AsyncMock()
        mock_resp = MagicMock()
        mock_resp.status_code = 400
        exc = httpx.HTTPStatusError("Bad Request", request=MagicMock(), response=mock_resp)
        client._client.post = AsyncMock(side_effect=exc)

        with pytest.raises(AuthenticationError):
            await client.authenticate()


class TestRequestSemaphoreEdge:
    @pytest.mark.asyncio
    async def test_401_retry_for_get(self):
        """GET 401 → re-auth and retry."""
        from src.core.utils.async_network import AsyncAPIClient
        from src.core.utils.network import SupersetAuthCache

        client = AsyncAPIClient({"base_url": "https://test.com", "auth": {}})
        client._client = AsyncMock()
        client._authenticated = True
        client._tokens = {"access_token": "old", "csrf_token": "old_csrf"}

        # Set up cache so re-auth succeeds
        SupersetAuthCache.set(
            client._auth_cache_key,
            {"access_token": "new_token", "csrf_token": "new_csrf"},
        )

        # First response 401, second 200
        first_resp = MagicMock()
        first_resp.status_code = 401
        second_resp = MagicMock()
        second_resp.status_code = 200
        second_resp.json.return_value = {"result": "retried"}
        client._client.request = AsyncMock(side_effect=[first_resp, second_resp])
        # CSRF refresh and login mocks for re-auth path
        mock_csrf_resp = MagicMock()
        mock_csrf_resp.is_success = True
        mock_csrf_resp.json.return_value = {"result": "csrf_refreshed"}
        client._client.get = AsyncMock(return_value=mock_csrf_resp)
        mock_login_resp = MagicMock()
        mock_login_resp.status_code = 200
        mock_login_resp.json.return_value = {"access_token": "login_token"}
        mock_login_resp.raise_for_status = MagicMock()
        client._client.post = AsyncMock(return_value=mock_login_resp)

        # Need to un-authenticate so get_headers calls authenticate
        client._authenticated = False

        result = await client.request(method="GET", endpoint="/chart/")
        assert result == {"result": "retried"}
        assert client._client.request.call_count == 2

    @pytest.mark.asyncio
    async def test_semaphore_acquire_release(self):
        """Semaphore acquired before request and released in finally."""
        from src.core.utils.async_network import AsyncAPIClient

        client = AsyncAPIClient({"base_url": "https://test.com", "auth": {}})
        client._client = AsyncMock()
        client._authenticated = True
        client._tokens = {"access_token": "a", "csrf_token": "b"}
        sem = asyncio.Semaphore(1)
        client._semaphore = sem

        mock_resp = MagicMock()
        mock_resp.status_code = 200
        mock_resp.json.return_value = {"ok": True}
        client._client.request = AsyncMock(return_value=mock_resp)

        result = await client.request(method="GET", endpoint="/test")
        assert result == {"ok": True}

    @pytest.mark.asyncio
    async def test_request_with_params(self):
        """Request passes params correctly."""
        from src.core.utils.async_network import AsyncAPIClient

        client = AsyncAPIClient({"base_url": "https://test.com", "auth": {}})
        client._client = AsyncMock()
        client._authenticated = True
        client._tokens = {"access_token": "a", "csrf_token": "b"}
        mock_resp = MagicMock()
        mock_resp.status_code = 200
        mock_resp.json.return_value = {"ok": True}
        client._client.request = AsyncMock(return_value=mock_resp)

        result = await client.request(method="GET", endpoint="/chart/", params={"page": 1})
        assert result == {"ok": True}

    @pytest.mark.asyncio
    async def test_http_status_error_generic_with_text(self):
        """Generic HTTP status error passes text."""
        from src.core.utils.async_network import AsyncAPIClient
        from src.core.utils.network import SupersetAPIError

        client = AsyncAPIClient({"base_url": "https://test.com", "auth": {}})
        client._client = AsyncMock()
        client._authenticated = True
        client._tokens = {"access_token": "a", "csrf_token": "b"}

        mock_resp = MagicMock()
        mock_resp.status_code = 500
        mock_resp.text = "Server error detail"
        exc = httpx.HTTPStatusError("500", request=MagicMock(), response=mock_resp)
        client._client.request = AsyncMock(side_effect=exc)

        with pytest.raises(SupersetAPIError, match="API Error 500"):
            await client.request(method="GET", endpoint="/chart/")


class TestUploadFileEdge:
    @pytest.mark.asyncio
    async def test_upload_non_200_response_no_json_error(self):
        """Upload with non-200 status but still valid JSON."""
        import io
        from src.core.utils.async_network import AsyncAPIClient

        client = AsyncAPIClient({"base_url": "https://test.com", "auth": {}})
        client._authenticated = True
        client._tokens = {"access_token": "a", "csrf_token": "b"}
        client._client = AsyncMock()

        mock_resp = MagicMock()
        mock_resp.status_code = 200
        mock_resp.json.side_effect = ValueError("parse error")
        client._client.post = AsyncMock(return_value=mock_resp)

        buf = io.BytesIO(b"zip")
        from src.core.utils.network import SupersetAPIError
        with pytest.raises(SupersetAPIError, match="API error during upload"):
            await client.upload_file(
                endpoint="/dashboard/import/",
                file_info={"file_obj": buf, "file_name": "test.zip"},
            )

    @pytest.mark.asyncio
    async def test_upload_with_path_string_non_200(self):
        """Upload with path string returns JSON from non-200."""
        from src.core.utils.async_network import AsyncAPIClient

        client = AsyncAPIClient({"base_url": "https://test.com", "auth": {}})
        client._authenticated = True
        client._tokens = {"access_token": "a", "csrf_token": "b"}
        client._client = AsyncMock()

        mock_resp = MagicMock()
        mock_resp.status_code = 200
        mock_resp.json.return_value = {"result": "imported"}
        client._client.post = AsyncMock(return_value=mock_resp)

        with patch("pathlib.Path.exists", return_value=True):
            with patch("pathlib.Path.read_bytes", return_value=b"zip content"):
                result = await client.upload_file(
                    endpoint="/dashboard/import/",
                    file_info={
                        "file_obj": "/tmp/test.zip",
                        "file_name": "test.zip",
                        "form_field": "formData",
                    },
                    extra_data={"overwrite": "true"},
                )
        assert result == {"result": "imported"}
# #endregion Test.AsyncNetwork.Edge
