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

import httpx
import pytest

from mistralai.workflows._version import __version__
from mistralai.workflows.client import WorkflowsClient
from mistralai.workflows.protocol.v1.events import (
    CustomTaskStarted,
    CustomTaskStartedAttributes,
    WorkflowEventResponse,
)


def _make_event() -> CustomTaskStarted:
    return CustomTaskStarted(
        event_id="evt-1",
        root_workflow_exec_id="root-1",
        workflow_exec_id="exec-1",
        workflow_run_id="run-1",
        workflow_name="test-workflow",
        attributes=CustomTaskStartedAttributes(custom_task_id="task-1", custom_task_type="test-task"),
    )


class TestWorkflowsClientUserAgent:
    @pytest.mark.asyncio
    async def test_default_user_agent_is_sent(self):
        """Verify the SDK User-Agent header is sent in requests."""
        captured_headers = {}

        async def capture_request(request: httpx.Request) -> httpx.Response:
            captured_headers.update(request.headers)
            return httpx.Response(200, json={"execution_id": "test"})

        client = WorkflowsClient(base_url="http://localhost", api_key="test-key")
        client.client._transport = httpx.MockTransport(capture_request)

        async with client:
            await client._raw_request("GET", "/test")

        assert captured_headers["user-agent"] == f"mistral-client-python/workflows-worker/{__version__}"

    @pytest.mark.asyncio
    async def test_custom_user_agent_is_preserved(self):
        """Verify a custom User-Agent header is not overwritten."""
        captured_headers = {}

        async def capture_request(request: httpx.Request) -> httpx.Response:
            captured_headers.update(request.headers)
            return httpx.Response(200, json={"execution_id": "test"})

        client = WorkflowsClient(
            base_url="http://localhost",
            api_key="test-key",
            headers={"User-Agent": "custom-agent/1.0"},
        )
        client.client._transport = httpx.MockTransport(capture_request)

        async with client:
            await client._raw_request("GET", "/test")

        assert captured_headers["user-agent"] == "custom-agent/1.0"

    @pytest.mark.asyncio
    async def test_custom_user_agent_case_insensitive(self):
        """Verify a custom user-agent header with different casing is preserved."""
        captured_headers = {}

        async def capture_request(request: httpx.Request) -> httpx.Response:
            captured_headers.update(request.headers)
            return httpx.Response(200, json={"execution_id": "test"})

        client = WorkflowsClient(
            base_url="http://localhost",
            api_key="test-key",
            headers={"user-agent": "custom-agent/2.0"},
        )
        client.client._transport = httpx.MockTransport(capture_request)

        async with client:
            await client._raw_request("GET", "/test")

        assert captured_headers["user-agent"] == "custom-agent/2.0"


class TestWorkflowsClientDeprecation:
    @pytest.mark.asyncio
    async def test_get_workflow_executions_emits_deprecation_warning(self):
        """Verify get_workflow_executions emits a DeprecationWarning.

        TODO: Remove this test when get_workflow_executions is removed.
        """
        client = WorkflowsClient(base_url="http://localhost", api_key="test-key")

        mock_response = MagicMock()
        mock_response.json.return_value = {"executions": [], "next_page_token": None}

        with patch.object(client, "_request", new_callable=AsyncMock) as mock_request:
            mock_request.return_value = mock_response

            with pytest.warns(DeprecationWarning, match="get_workflow_runs"):
                await client.get_workflow_executions()

    @pytest.mark.asyncio
    async def test_get_workflow_runs_does_not_emit_deprecation_warning(self):
        """Verify get_workflow_runs does NOT emit a DeprecationWarning.

        TODO: Remove this test when get_workflow_executions is removed.
        """
        client = WorkflowsClient(base_url="http://localhost", api_key="test-key")

        mock_response = MagicMock()
        mock_response.json.return_value = {"executions": [], "next_page_token": None}

        with patch.object(client, "_request", new_callable=AsyncMock) as mock_request:
            mock_request.return_value = mock_response

            with warnings.catch_warnings():
                warnings.simplefilter("error")
                await client.get_workflow_runs()


class TestSendEventsBatchFallback:
    @pytest.mark.asyncio
    async def test_fallback_on_404(self):
        """When the batch endpoint returns 404, fall back to individual sends."""
        client = WorkflowsClient(base_url="http://localhost", api_key="test-key")
        events = [_make_event(), _make_event()]
        assert client._batch_events_supported

        with (
            patch.object(
                client,
                "_request",
                new_callable=AsyncMock,
                side_effect=httpx.HTTPStatusError(
                    "Not Found", request=httpx.Request("POST", "http://localhost"), response=httpx.Response(404)
                ),
            ),
            patch.object(
                client, "send_event", new_callable=AsyncMock, return_value=WorkflowEventResponse(status="success")
            ) as mock_send_event,
        ):
            result = await client.send_events_batch(events)

        assert result.status == "success"
        assert result.events_received == 2
        assert mock_send_event.call_count == 2
        assert not client._batch_events_supported

    @pytest.mark.asyncio
    async def test_subsequent_calls_skip_batch_endpoint(self):
        """After a 404, subsequent calls should use the fallback directly without hitting _request."""
        client = WorkflowsClient(base_url="http://localhost", api_key="test-key")
        events = [_make_event(), _make_event()]
        assert client._batch_events_supported

        # First call: trigger fallback via 404
        with (
            patch.object(
                client,
                "_request",
                new_callable=AsyncMock,
                side_effect=httpx.HTTPStatusError(
                    "Not Found", request=httpx.Request("POST", "http://localhost"), response=httpx.Response(404)
                ),
            ),
            patch.object(
                client, "send_event", new_callable=AsyncMock, return_value=WorkflowEventResponse(status="success")
            ),
        ):
            await client.send_events_batch(events)

        # Second call: _request should not be called
        with (
            patch.object(client, "_request", new_callable=AsyncMock) as mock_request,
            patch.object(
                client, "send_event", new_callable=AsyncMock, return_value=WorkflowEventResponse(status="success")
            ) as mock_send_event,
        ):
            result = await client.send_events_batch(events)

        mock_request.assert_not_called()
        assert mock_send_event.call_count == 2
        assert result.status == "success"

    @pytest.mark.asyncio
    async def test_non_404_error_is_raised(self):
        """Non-404 errors should be raised normally, not trigger fallback."""
        client = WorkflowsClient(base_url="http://localhost", api_key="test-key")
        events = [_make_event()]
        assert client._batch_events_supported

        with (
            patch.object(
                client,
                "_request",
                new_callable=AsyncMock,
                side_effect=httpx.HTTPStatusError(
                    "Server Error", request=httpx.Request("POST", "http://localhost"), response=httpx.Response(500)
                ),
            ),
            pytest.raises(Exception),
        ):
            await client.send_events_batch(events)

        assert client._batch_events_supported
