"""Tests for API response handling with mocked HTTP.

Verifies that tools correctly process API responses, handle errors,
and return safe, well-formed JSON to the LLM.
"""

import json
from unittest.mock import patch, AsyncMock

import pytest

from influ2_mcp.tools.cohorts import influ2_list_cohorts, influ2_get_cohort_details
from influ2_mcp.tools.targets import influ2_manage_targets


def _mock_api_client(get_responses=None, post_responses=None, put_responses=None):
    """Create a mock API client with preconfigured responses."""
    client = AsyncMock()
    if get_responses:
        client.get = AsyncMock(side_effect=get_responses)
    if post_responses:
        client.post = AsyncMock(side_effect=post_responses)
    if put_responses:
        client.put = AsyncMock(side_effect=put_responses)
    return client


class TestListCohortsResponse:
    """Test influ2_list_cohorts handles various API responses."""

    @pytest.mark.asyncio
    async def test_returns_valid_json(self):
        mock_response = {
            "data": [
                {"cohort_id": "c1", "name": "Campaign A", "targets": []},
                {"cohort_id": "c2", "name": "Campaign B", "targets": [{"id": "t1"}]},
            ],
            "paging": {"total": 2},
        }
        with (
            patch("influ2_mcp.tools.cohorts.resolve_client_id", return_value="test-client"),
            patch("influ2_mcp.tools.cohorts.get_api_client", return_value=_mock_api_client(get_responses=[mock_response])),
        ):
            result = await influ2_list_cohorts(client_id="test-client")
            data = json.loads(result)

            assert "cohorts" in data
            assert data["count"] == 2
            assert "_next_steps" in data

    @pytest.mark.asyncio
    async def test_strips_targets_from_list(self):
        """Target arrays must be stripped even from list responses."""
        targets = [{"target_id": f"t-{i}"} for i in range(500)]
        mock_response = {
            "data": [{"cohort_id": "c1", "name": "Big", "targets": targets}],
        }
        with (
            patch("influ2_mcp.tools.cohorts.resolve_client_id", return_value="test-client"),
            patch("influ2_mcp.tools.cohorts.get_api_client", return_value=_mock_api_client(get_responses=[mock_response])),
        ):
            result = await influ2_list_cohorts(client_id="test-client")
            data = json.loads(result)

            cohort = data["cohorts"][0]
            assert "targets" not in cohort
            assert cohort["target_count"] == 500

    @pytest.mark.asyncio
    async def test_empty_cohorts(self):
        mock_response = {"data": [], "paging": {"total": 0}}
        with (
            patch("influ2_mcp.tools.cohorts.resolve_client_id", return_value="test-client"),
            patch("influ2_mcp.tools.cohorts.get_api_client", return_value=_mock_api_client(get_responses=[mock_response])),
        ):
            result = await influ2_list_cohorts(client_id="test-client")
            data = json.loads(result)
            assert data["count"] == 0
            assert "Influ2 UI" in data["_next_steps"][0]


class TestGetCohortDetailsResponse:
    """Test influ2_get_cohort_details compound tool."""

    @pytest.mark.asyncio
    async def test_combines_cohort_metrics_contents(self):
        cohort_resp = {"cohort_id": "c1", "name": "Test", "targets": []}
        metrics_resp = {"impressions": 5000, "clicks": 10, "ctr": 0.002}
        contents_resp = [{"id": "content-1", "name": "Ad 1"}]

        with (
            patch("influ2_mcp.tools.cohorts.resolve_client_id", return_value="test-client"),
            patch("influ2_mcp.tools.cohorts.get_api_client", return_value=_mock_api_client(
                get_responses=[cohort_resp, metrics_resp, contents_resp]
            )),
        ):
            result = await influ2_get_cohort_details(
                cohort_id="c1", client_id="test-client"
            )
            data = json.loads(result)

            assert "cohort" in data
            assert "metrics" in data
            assert "contents" in data

    @pytest.mark.asyncio
    async def test_strips_targets_from_details(self):
        targets = [{"target_id": f"t-{i}"} for i in range(1000)]
        cohort_resp = {"cohort_id": "c1", "name": "Big", "targets": targets}

        with (
            patch("influ2_mcp.tools.cohorts.resolve_client_id", return_value="test-client"),
            patch("influ2_mcp.tools.cohorts.get_api_client", return_value=_mock_api_client(
                get_responses=[cohort_resp]
            )),
        ):
            result = await influ2_get_cohort_details(
                cohort_id="c1",
                client_id="test-client",
                include_metrics=False,
                include_contents=False,
            )
            data = json.loads(result)

            assert "targets" not in data["cohort"]
            assert data["cohort"]["target_count"] == 1000

    @pytest.mark.asyncio
    async def test_handles_metrics_failure_gracefully(self):
        """If metrics endpoint fails, the rest should still work."""
        cohort_resp = {"cohort_id": "c1", "name": "Test", "targets": []}
        contents_resp = [{"id": "content-1"}]

        async def mock_get(path, params=None):
            if "metrics" in path:
                raise Exception("Metrics API down")
            if "contents" in path:
                return contents_resp
            return cohort_resp

        mock_client = AsyncMock()
        mock_client.get = mock_get

        with (
            patch("influ2_mcp.tools.cohorts.resolve_client_id", return_value="test-client"),
            patch("influ2_mcp.tools.cohorts.get_api_client", return_value=mock_client),
        ):
            result = await influ2_get_cohort_details(
                cohort_id="c1", client_id="test-client"
            )
            data = json.loads(result)

            # Metrics should have error, but contents and cohort should be fine
            assert "_error" in data["metrics"]
            assert "contents" in data
            assert "cohort" in data

    @pytest.mark.asyncio
    async def test_response_under_1mb(self):
        """Even with large targets, response must be under 1MB."""
        targets = [
            {
                "target_id": f"t-{i}",
                "external_id": f"CRM-{i}",
                "first_name": f"Person{i}",
                "last_name": "Test",
                "email": f"p{i}@test.com",
                "company_name": "Acme",
            }
            for i in range(10_000)
        ]
        cohort_resp = {"cohort_id": "c1", "name": "Huge", "targets": targets}

        with (
            patch("influ2_mcp.tools.cohorts.resolve_client_id", return_value="test-client"),
            patch("influ2_mcp.tools.cohorts.get_api_client", return_value=_mock_api_client(
                get_responses=[cohort_resp]
            )),
        ):
            result = await influ2_get_cohort_details(
                cohort_id="c1",
                client_id="test-client",
                include_metrics=False,
                include_contents=False,
            )
            assert len(result) < 1_000_000, f"Response is {len(result):,} bytes"


class TestManageTargetsResponse:
    """Test influ2_manage_targets tool."""

    @pytest.mark.asyncio
    async def test_invalid_json_returns_error(self):
        with (
            patch("influ2_mcp.tools.targets.resolve_client_id", return_value="test-client"),
            patch("influ2_mcp.tools.targets.get_api_client", return_value=AsyncMock()),
        ):
            result = await influ2_manage_targets(
                cohort_id="c1",
                action="upsert",
                targets="not valid json",
                client_id="test-client",
            )
            data = json.loads(result)
            assert data["error"] is True
            assert "Invalid JSON" in data["message"]

    @pytest.mark.asyncio
    async def test_upsert_calls_correct_endpoint(self):
        mock_client = _mock_api_client(
            post_responses=[{"id": "job-1", "status": "completed"}]
        )
        with (
            patch("influ2_mcp.tools.targets.resolve_client_id", return_value="test-client"),
            patch("influ2_mcp.tools.targets.get_api_client", return_value=mock_client),
            patch("influ2_mcp.tools.targets.poll_job_until_complete", new_callable=AsyncMock, return_value={"status": "completed"}),
        ):
            result = await influ2_manage_targets(
                cohort_id="c1",
                action="upsert",
                targets='[{"external_id": "CRM-1"}]',
                client_id="test-client",
            )
            # Verify it called the upsert endpoint
            call_args = mock_client.post.call_args
            assert "upsert" in call_args[0][0]

    @pytest.mark.asyncio
    async def test_replace_calls_put_endpoint(self):
        mock_client = _mock_api_client(
            put_responses=[{"id": "job-2", "status": "completed"}]
        )
        with (
            patch("influ2_mcp.tools.targets.resolve_client_id", return_value="test-client"),
            patch("influ2_mcp.tools.targets.get_api_client", return_value=mock_client),
            patch("influ2_mcp.tools.targets.poll_job_until_complete", new_callable=AsyncMock, return_value={"status": "completed"}),
        ):
            result = await influ2_manage_targets(
                cohort_id="c1",
                action="replace",
                targets='[{"external_id": "CRM-1"}]',
                client_id="test-client",
            )
            # Verify it used PUT (replace endpoint)
            assert mock_client.put.called

    @pytest.mark.asyncio
    async def test_delete_calls_delete_endpoint(self):
        mock_client = _mock_api_client(
            post_responses=[{"id": "job-3", "status": "completed"}]
        )
        with (
            patch("influ2_mcp.tools.targets.resolve_client_id", return_value="test-client"),
            patch("influ2_mcp.tools.targets.get_api_client", return_value=mock_client),
            patch("influ2_mcp.tools.targets.poll_job_until_complete", new_callable=AsyncMock, return_value={"status": "completed"}),
        ):
            result = await influ2_manage_targets(
                cohort_id="c1",
                action="delete",
                targets='[{"external_id": "CRM-1"}]',
                client_id="test-client",
            )
            call_args = mock_client.post.call_args
            assert "delete" in call_args[0][0]
