"""Compatibility tests for queue engine contract mapping."""

from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from motia.runtime import Motia
from motia.triggers import http, queue
from motia.types import ApiRequest, ApiResponse, FlowContext, QueueConfig, StepConfig


@pytest.fixture
def mock_bridge() -> MagicMock:
    """Create a bridge mock with async call support."""
    bridge = MagicMock()
    bridge.register_function = MagicMock()
    bridge.register_trigger = MagicMock()
    bridge.call = AsyncMock()
    return bridge


@pytest.fixture
def mock_logger() -> MagicMock:
    """Create mock logger."""
    return MagicMock()


def test_queue_trigger_registers_as_queue_trigger(mock_bridge: MagicMock, mock_logger: MagicMock) -> None:
    """Queue trigger should map to queue trigger type for engine registration."""
    config = StepConfig(
        name="queue-trigger-compat",
        triggers=[queue("orders.created")],
    )

    async def handler(input_data: object, ctx: FlowContext[object]) -> None:
        _ = (input_data, ctx)

    with (
        patch("motia.runtime.current_trace_id", return_value="test-trace-id"),
        patch("motia.runtime.Logger", return_value=mock_logger),
        patch("motia.runtime.get_instance", return_value=mock_bridge),
    ):
        motia = Motia()
        motia.add_step(config, "steps/test_step.py", handler)

    call_args = mock_bridge.register_trigger.call_args
    assert call_args[0][0] == "queue"
    assert call_args[0][2]["topic"] == "orders.created"


@pytest.mark.asyncio
async def test_ctx_enqueue_uses_enqueue_for_queue_triggers(mock_bridge: MagicMock, mock_logger: MagicMock) -> None:
    """ctx.enqueue should invoke queue enqueue function for queue-trigger handlers."""
    config = StepConfig(
        name="queue-enqueue-compat",
        triggers=[queue("orders.created")],
    )

    async def handler(input_data: object, ctx: FlowContext[object]) -> None:
        _ = input_data
        await ctx.enqueue(
            {
                "topic": "orders.processed",
                "data": {"order_id": "123"},
            }
        )

    with (
        patch("motia.runtime.current_trace_id", return_value="test-trace-id"),
        patch("motia.runtime.Logger", return_value=mock_logger),
        patch("motia.runtime.get_instance", return_value=mock_bridge),
    ):
        motia = Motia()
        motia.add_step(config, "steps/test_step.py", handler)
        registered_handler = mock_bridge.register_function.call_args_list[0][0][1]
        await registered_handler({"source": "test"})

    mock_bridge.call.assert_awaited_once_with(
        "enqueue",
        {
            "topic": "orders.processed",
            "data": {"order_id": "123"},
        },
    )


@pytest.mark.asyncio
async def test_ctx_enqueue_uses_enqueue_for_api_triggers(mock_bridge: MagicMock, mock_logger: MagicMock) -> None:
    """ctx.enqueue should invoke queue enqueue function for API-trigger handlers too."""
    config = StepConfig(
        name="api-emit-compat",
        triggers=[http("POST", "/orders")],
    )

    async def handler(req: ApiRequest[dict], ctx: FlowContext[dict]) -> ApiResponse[dict]:
        _ = req
        await ctx.enqueue(
            {
                "topic": "orders.created",
                "data": {"order_id": "abc"},
            }
        )
        return ApiResponse(status=200, body={"ok": True})

    with (
        patch("motia.runtime.current_trace_id", return_value="test-trace-id"),
        patch("motia.runtime.Logger", return_value=mock_logger),
        patch("motia.runtime.get_instance", return_value=mock_bridge),
    ):
        motia = Motia()
        motia.add_step(config, "steps/test_step.py", handler)
        registered_handler = mock_bridge.register_function.call_args_list[0][0][1]
        response_writer = MagicMock()
        response_writer.send_message_async = AsyncMock()
        response_writer.close = MagicMock()
        response_writer.stream = MagicMock()
        response_writer.stream.write = MagicMock()
        request_body_reader = MagicMock()
        await registered_handler(
            {
                "path": "/orders",
                "method": "POST",
                "path_params": {},
                "query_params": {},
                "body": {"hello": "world"},
                "headers": {},
                "response": response_writer,
                "request_body": request_body_reader,
            }
        )

    mock_bridge.call.assert_awaited_once_with(
        "enqueue",
        {
            "topic": "orders.created",
            "data": {"order_id": "abc"},
        },
    )


@pytest.mark.asyncio
async def test_runtime_context_enqueue_uses_enqueue(mock_bridge: MagicMock, mock_logger: MagicMock) -> None:
    """Runtime-built context should enqueue using the III instance call method."""
    config = StepConfig(
        name="runtime-enqueue-test",
        triggers=[queue("runtime.topic")],
    )

    captured_ctx = None

    async def handler(input_data: object, ctx: FlowContext[object]) -> None:
        nonlocal captured_ctx
        captured_ctx = ctx
        await ctx.enqueue({"topic": "runtime.topic", "data": {"value": 1}})

    with (
        patch("motia.runtime.current_trace_id", return_value="test-trace-id"),
        patch("motia.runtime.Logger", return_value=mock_logger),
        patch("motia.runtime.get_instance", return_value=mock_bridge),
    ):
        motia = Motia()
        motia.add_step(config, "steps/test_step.py", handler)
        registered_handler = mock_bridge.register_function.call_args_list[0][0][1]
        await registered_handler({"source": "test"})

    assert captured_ctx is not None
    mock_bridge.call.assert_awaited_once_with(
        "enqueue",
        {"topic": "runtime.topic", "data": {"value": 1}},
    )


def test_queue_trigger_passes_queue_config(mock_bridge: MagicMock, mock_logger: MagicMock) -> None:
    """Queue trigger with config should include it as queue_config."""
    config = StepConfig(
        name="queue-config-test",
        triggers=[
            queue("orders", config=QueueConfig(max_retries=5, type="fifo")),
        ],
    )

    async def handler(input_data: object, ctx: FlowContext[object]) -> None:
        _ = (input_data, ctx)

    with (
        patch("motia.runtime.current_trace_id", return_value="test-trace-id"),
        patch("motia.runtime.Logger", return_value=mock_logger),
        patch("motia.runtime.get_instance", return_value=mock_bridge),
    ):
        motia = Motia()
        motia.add_step(config, "steps/test_step.py", handler)

    call_args = mock_bridge.register_trigger.call_args
    trigger_config = call_args[0][2]
    assert trigger_config["queue_config"]["maxRetries"] == 5
    assert trigger_config["queue_config"]["type"] == "fifo"


def test_queue_trigger_omits_queue_config_when_not_provided(mock_bridge: MagicMock, mock_logger: MagicMock) -> None:
    """Queue trigger without config should not include queue_config."""
    config = StepConfig(
        name="queue-no-config-test",
        triggers=[queue("orders")],
    )

    async def handler(input_data: object, ctx: FlowContext[object]) -> None:
        _ = (input_data, ctx)

    with (
        patch("motia.runtime.current_trace_id", return_value="test-trace-id"),
        patch("motia.runtime.Logger", return_value=mock_logger),
        patch("motia.runtime.get_instance", return_value=mock_bridge),
    ):
        motia = Motia()
        motia.add_step(config, "steps/test_step.py", handler)

    call_args = mock_bridge.register_trigger.call_args
    trigger_config = call_args[0][2]
    assert "queue_config" not in trigger_config
