"""Unit tests for FMPFundamentalProvider 402 reason recording."""

from __future__ import annotations

from unittest.mock import AsyncMock, patch

import httpx
import pytest

from app.providers.fmp import FMPFundamentalProvider


def _mock_response(status_code: int, json_data: object = None) -> httpx.Response:
    """Build a fake httpx.Response."""
    resp = httpx.Response(
        status_code=status_code,
        json=json_data if json_data is not None else {},
        request=httpx.Request("GET", "https://example.com"),
    )
    return resp


@pytest.fixture
def provider() -> FMPFundamentalProvider:
    return FMPFundamentalProvider(api_key="test-key")


class TestFetchJsonOptional402Tracking:
    """_fetch_json_optional returns (data, was_402) tuple."""

    @pytest.mark.asyncio
    async def test_returns_empty_dict_and_true_on_402(self, provider):
        mock_client = AsyncMock()
        mock_client.get.return_value = _mock_response(402)

        data, was_402 = await provider._fetch_json_optional(
            mock_client, "ratios-ttm", {}, "AAPL"
        )

        assert data == {}
        assert was_402 is True

    @pytest.mark.asyncio
    async def test_returns_data_and_false_on_200(self, provider):
        mock_client = AsyncMock()
        mock_client.get.return_value = _mock_response(
            200, [{"priceToEarningsRatioTTM": 25.5}]
        )

        data, was_402 = await provider._fetch_json_optional(
            mock_client, "ratios-ttm", {}, "AAPL"
        )

        assert data == {"priceToEarningsRatioTTM": 25.5}
        assert was_402 is False


class TestFetchFundamentals402Recording:
    """fetch_fundamentals records 402 endpoints in unavailable_fields."""

    @pytest.mark.asyncio
    async def test_all_402_records_all_fields(self, provider):
        """When all supplementary endpoints return 402, all three fields are recorded."""
        profile_resp = _mock_response(200, [{"marketCap": 1_000_000}])
        ratios_resp = _mock_response(402)
        growth_resp = _mock_response(402)
        earnings_resp = _mock_response(402)

        async def mock_get(url, params=None):
            if "profile" in url:
                return profile_resp
            if "ratios-ttm" in url:
                return ratios_resp
            if "financial-growth" in url:
                return growth_resp
            if "earnings" in url:
                return earnings_resp
            return _mock_response(200, [{}])

        with patch("app.providers.fmp.httpx.AsyncClient") as MockClient:
            instance = AsyncMock()
            instance.get.side_effect = mock_get
            instance.__aenter__ = AsyncMock(return_value=instance)
            instance.__aexit__ = AsyncMock(return_value=False)
            MockClient.return_value = instance

            result = await provider.fetch_fundamentals("AAPL")

        assert result.unavailable_fields == {
            "pe_ratio": "requires paid plan",
            "revenue_growth": "requires paid plan",
            "earnings_surprise": "requires paid plan",
        }

    @pytest.mark.asyncio
    async def test_mixed_200_402_records_only_402_fields(self, provider):
        """When only ratios-ttm returns 402, only pe_ratio is recorded."""
        profile_resp = _mock_response(200, [{"marketCap": 2_000_000}])
        ratios_resp = _mock_response(402)
        growth_resp = _mock_response(200, [{"revenueGrowth": 0.15}])
        earnings_resp = _mock_response(200, [{"epsActual": 3.0, "epsEstimated": 2.5}])

        async def mock_get(url, params=None):
            if "profile" in url:
                return profile_resp
            if "ratios-ttm" in url:
                return ratios_resp
            if "financial-growth" in url:
                return growth_resp
            if "earnings" in url:
                return earnings_resp
            return _mock_response(200, [{}])

        with patch("app.providers.fmp.httpx.AsyncClient") as MockClient:
            instance = AsyncMock()
            instance.get.side_effect = mock_get
            instance.__aenter__ = AsyncMock(return_value=instance)
            instance.__aexit__ = AsyncMock(return_value=False)
            MockClient.return_value = instance

            result = await provider.fetch_fundamentals("AAPL")

        assert result.unavailable_fields == {"pe_ratio": "requires paid plan"}
        assert result.revenue_growth == 0.15
        assert result.earnings_surprise is not None

    @pytest.mark.asyncio
    async def test_no_402_empty_unavailable_fields(self, provider):
        """When all endpoints succeed, unavailable_fields is empty."""
        profile_resp = _mock_response(200, [{"marketCap": 3_000_000}])
        ratios_resp = _mock_response(200, [{"priceToEarningsRatioTTM": 20.0}])
        growth_resp = _mock_response(200, [{"revenueGrowth": 0.10}])
        earnings_resp = _mock_response(200, [{"epsActual": 2.0, "epsEstimated": 1.8}])

        async def mock_get(url, params=None):
            if "profile" in url:
                return profile_resp
            if "ratios-ttm" in url:
                return ratios_resp
            if "financial-growth" in url:
                return growth_resp
            if "earnings" in url:
                return earnings_resp
            return _mock_response(200, [{}])

        with patch("app.providers.fmp.httpx.AsyncClient") as MockClient:
            instance = AsyncMock()
            instance.get.side_effect = mock_get
            instance.__aenter__ = AsyncMock(return_value=instance)
            instance.__aexit__ = AsyncMock(return_value=False)
            MockClient.return_value = instance

            result = await provider.fetch_fundamentals("AAPL")

        assert result.unavailable_fields == {}
        assert result.pe_ratio == 20.0
