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

import httpx
import pytest

from app.services.ai_engine.base import AIEngineAdapter, AIQueryResult, EngineType
from app.services.ai_engine.chatgpt import ChatGPTAdapter
from app.services.ai_engine.deepseek import DeepSeekAdapter
from app.services.ai_engine.perplexity import PerplexityAdapter


class _ConcreteAdapter(AIEngineAdapter):
    async def query(self, query, brand_name, competitor_names=None):
        pass

    def get_engine_type(self):
        return EngineType.CHATGPT

    def _get_env_key(self) -> str | None:
        return None


class TestProxySupportBase:
    def test_base_init_with_proxy(self):
        adapter = _ConcreteAdapter(api_key="test-key", proxy="http://proxy:8080")
        assert adapter.proxy == "http://proxy:8080"

    def test_base_init_proxy_default_none(self):
        adapter = _ConcreteAdapter(api_key="test-key")
        assert adapter.proxy is None

    def test_base_init_proxy_from_https_proxy_env(self):
        with patch.dict(os.environ, {"HTTPS_PROXY": "http://env-proxy:3128"}):
            adapter = _ConcreteAdapter(api_key="test-key")
            assert adapter.proxy == "http://env-proxy:3128"

    def test_base_init_proxy_from_https_proxy_lowercase_env(self):
        with patch.dict(os.environ, {"https_proxy": "http://lower-proxy:3128"}, clear=False):
            if "HTTPS_PROXY" in os.environ:
                del os.environ["HTTPS_PROXY"]
            adapter = _ConcreteAdapter(api_key="test-key")
            assert adapter.proxy == "http://lower-proxy:3128"

    def test_base_init_explicit_proxy_overrides_env(self):
        with patch.dict(os.environ, {"HTTPS_PROXY": "http://env-proxy:3128"}):
            adapter = _ConcreteAdapter(
                api_key="test-key", proxy="http://explicit-proxy:8080"
            )
            assert adapter.proxy == "http://explicit-proxy:8080"

    @pytest.mark.asyncio
    async def test_get_client_with_proxy(self):
        adapter = _ConcreteAdapter(api_key="test-key", proxy="http://proxy:8080")
        client = await adapter._get_client()
        assert isinstance(client, httpx.AsyncClient)
        await adapter.close()

    @pytest.mark.asyncio
    async def test_get_client_without_proxy(self):
        adapter = _ConcreteAdapter(api_key="test-key")
        client = await adapter._get_client()
        assert isinstance(client, httpx.AsyncClient)
        await adapter.close()


class TestChatGPTProxy:
    def test_chatgpt_proxy_parameter(self):
        adapter = ChatGPTAdapter(api_key="test-key", proxy="http://proxy:8080")
        assert adapter.proxy == "http://proxy:8080"

    def test_chatgpt_proxy_from_openai_proxy_env(self):
        with patch.dict(os.environ, {"OPENAI_PROXY": "http://openai-proxy:8080"}):
            adapter = ChatGPTAdapter(api_key="test-key")
            assert adapter.proxy == "http://openai-proxy:8080"

    def test_chatgpt_explicit_proxy_overrides_env(self):
        with patch.dict(os.environ, {"OPENAI_PROXY": "http://env-proxy:8080"}):
            adapter = ChatGPTAdapter(
                api_key="test-key", proxy="http://explicit:9090"
            )
            assert adapter.proxy == "http://explicit:9090"

    def test_chatgpt_fallback_to_https_proxy_env(self):
        with patch.dict(os.environ, {"HTTPS_PROXY": "http://fallback:3128"}, clear=False):
            if "OPENAI_PROXY" in os.environ:
                del os.environ["OPENAI_PROXY"]
            adapter = ChatGPTAdapter(api_key="test-key")
            assert adapter.proxy == "http://fallback:3128"

    def test_chatgpt_no_proxy(self):
        with patch.dict(os.environ, {}, clear=False):
            for key in ("OPENAI_PROXY", "HTTPS_PROXY", "https_proxy"):
                os.environ.pop(key, None)
            adapter = ChatGPTAdapter(api_key="test-key")
            assert adapter.proxy is None


class TestPerplexityProxy:
    def test_perplexity_proxy_parameter(self):
        adapter = PerplexityAdapter(api_key="test-key", proxy="http://proxy:8080")
        assert adapter.proxy == "http://proxy:8080"

    def test_perplexity_proxy_from_perplexity_proxy_env(self):
        with patch.dict(os.environ, {"PERPLEXITY_PROXY": "http://pplx-proxy:8080"}):
            adapter = PerplexityAdapter(api_key="test-key")
            assert adapter.proxy == "http://pplx-proxy:8080"

    def test_perplexity_fallback_to_https_proxy_env(self):
        with patch.dict(os.environ, {"HTTPS_PROXY": "http://fallback:3128"}, clear=False):
            if "PERPLEXITY_PROXY" in os.environ:
                del os.environ["PERPLEXITY_PROXY"]
            adapter = PerplexityAdapter(api_key="test-key")
            assert adapter.proxy == "http://fallback:3128"


class TestDeepSeekAdapter:
    def test_deepseek_init_default(self):
        with patch.dict(os.environ, {"DEEPSEEK_API_KEY": "ds-test-key"}):
            adapter = DeepSeekAdapter()
            assert adapter.api_key == "ds-test-key"
            assert adapter.get_engine_type() == EngineType.DEEPSEEK

    def test_deepseek_init_with_params(self):
        adapter = DeepSeekAdapter(
            api_key="custom-key",
            model="deepseek-reasoner",
            base_url="https://custom.api.com/v1",
        )
        assert adapter.api_key == "custom-key"
        assert adapter._model == "deepseek-reasoner"
        assert adapter._base_url == "https://custom.api.com/v1"

    def test_deepseek_init_from_env(self):
        with patch.dict(
            os.environ,
            {
                "DEEPSEEK_API_KEY": "env-key",
                "DEEPSEEK_MODEL": "deepseek-coder",
                "DEEPSEEK_BASE_URL": "https://env.api.com/v1",
            },
        ):
            adapter = DeepSeekAdapter()
            assert adapter.api_key == "env-key"
            assert adapter._model == "deepseek-coder"
            assert adapter._base_url == "https://env.api.com/v1"

    def test_deepseek_default_model(self):
        adapter = DeepSeekAdapter(api_key="test-key")
        assert adapter._model == "deepseek-chat"

    def test_deepseek_default_base_url(self):
        adapter = DeepSeekAdapter(api_key="test-key")
        assert adapter._base_url == "https://api.deepseek.com/v1"

    def test_deepseek_endpoint(self):
        adapter = DeepSeekAdapter(api_key="test-key")
        assert adapter._endpoint == "https://api.deepseek.com/v1/chat/completions"

    def test_deepseek_engine_type(self):
        adapter = DeepSeekAdapter(api_key="test-key")
        assert adapter.get_engine_type() == EngineType.DEEPSEEK

    def test_deepseek_proxy_parameter(self):
        adapter = DeepSeekAdapter(api_key="test-key", proxy="http://proxy:8080")
        assert adapter.proxy == "http://proxy:8080"

    def test_deepseek_proxy_from_deepseek_proxy_env(self):
        with patch.dict(os.environ, {"DEEPSEEK_PROXY": "http://ds-proxy:8080"}):
            adapter = DeepSeekAdapter(api_key="test-key")
            assert adapter.proxy == "http://ds-proxy:8080"

    @pytest.mark.asyncio
    async def test_deepseek_query_success(self):
        adapter = DeepSeekAdapter(api_key="test-key")

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "choices": [
                {"message": {"content": "BrandX is a leading AI company with great models."}}
            ],
            "model": "deepseek-chat",
            "usage": {"prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30},
        }

        with patch.object(adapter._client, "post", new_callable=AsyncMock) as mock_post:
            mock_post.return_value = mock_response
            result = await adapter.query(
                query="best AI companies",
                brand_name="BrandX",
                competitor_names=["CompetitorY"],
            )

        assert isinstance(result, AIQueryResult)
        assert result.engine_type == EngineType.DEEPSEEK
        assert result.query == "best AI companies"
        assert "BrandX" in result.raw_response
        assert result.has_brand_citation is True
        assert result.has_competitor_citation is False
        assert result.response_time_ms >= 0

    @pytest.mark.asyncio
    async def test_deepseek_query_with_competitor(self):
        adapter = DeepSeekAdapter(api_key="test-key")

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "choices": [
                {"message": {"content": "BrandX and CompetitorY are both strong AI companies."}}
            ],
            "model": "deepseek-chat",
        }

        with patch.object(adapter._client, "post", new_callable=AsyncMock) as mock_post:
            mock_post.return_value = mock_response
            result = await adapter.query(
                query="AI comparison",
                brand_name="BrandX",
                competitor_names=["CompetitorY"],
            )

        assert result.has_brand_citation is True
        assert result.has_competitor_citation is True
        assert len(result.competitor_contexts) == 1

    @pytest.mark.asyncio
    async def test_deepseek_query_with_rate_limiter(self):
        mock_limiter = AsyncMock()
        adapter = DeepSeekAdapter(api_key="test-key", rate_limiter=mock_limiter)

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "choices": [{"message": {"content": "Some response"}}],
            "model": "deepseek-chat",
        }

        with patch.object(adapter._client, "post", new_callable=AsyncMock) as mock_post:
            mock_post.return_value = mock_response
            await adapter.query(query="test", brand_name="BrandX")

        mock_limiter.acquire.assert_awaited()

    @pytest.mark.asyncio
    async def test_deepseek_query_api_error(self):
        adapter = DeepSeekAdapter(api_key="test-key")

        mock_response = MagicMock()
        mock_response.status_code = 401
        mock_response.text = "Unauthorized"

        with patch.object(adapter._client, "post", new_callable=AsyncMock) as mock_post:
            mock_post.return_value = mock_response

            with pytest.raises(Exception):
                await adapter.query(query="test", brand_name="BrandX")

    @pytest.mark.asyncio
    async def test_deepseek_query_transport_error(self):
        adapter = DeepSeekAdapter(api_key="test-key")

        with patch.object(adapter._client, "post", new_callable=AsyncMock) as mock_post:
            mock_post.side_effect = httpx.TransportError("Connection failed")

            with pytest.raises(Exception):
                await adapter.query(query="test", brand_name="BrandX")

    @pytest.mark.asyncio
    async def test_deepseek_query_timeout(self):
        adapter = DeepSeekAdapter(api_key="test-key")

        with patch.object(adapter._client, "post", new_callable=AsyncMock) as mock_post:
            mock_post.side_effect = httpx.TimeoutException("Request timed out")

            with pytest.raises(Exception):
                await adapter.query(query="test", brand_name="BrandX")

    @pytest.mark.asyncio
    async def test_deepseek_close(self):
        adapter = DeepSeekAdapter(api_key="test-key")
        await adapter.close()

    @pytest.mark.asyncio
    async def test_deepseek_context_manager(self):
        async with DeepSeekAdapter(api_key="test-key") as adapter:
            assert adapter.api_key == "test-key"
