"""Tests for server-level dynamic replanning flow."""

from __future__ import annotations

import sys
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock

import pytest

ROOT = Path(__file__).resolve().parents[2]
if str(ROOT) not in sys.path:
    sys.path.insert(0, str(ROOT))

from src.executor.action_executor import ExecutionReport
from src.server import OrchestratorServer, ServerConfig


@pytest.mark.asyncio
async def test_execute_plan_and_transition_dynamic_replan_success() -> None:
    config = ServerConfig(enable_planner=True, enable_executor=True)

    mock_executor = MagicMock()
    mock_executor.register_broadcast_callback = MagicMock()
    mock_executor.cancel = MagicMock()
    mock_executor.reset_cancel = MagicMock()

    call_count = 0

    async def execute_plan_side_effect(plan_arg, *args, **kwargs):
        nonlocal call_count
        call_count += 1
        if call_count == 1:
            callback = kwargs.get("on_replan_requested")
            assert callback is not None
            callback(
                {
                    "type": "dynamic_replan_requested",
                    "step_id": "scan1",
                    "action": "SCAN_AREA",
                    "reasons": ["analysis_keywords:chemical,spill"],
                    "scan_output": {"interesting": True},
                    "plan_id": "original_plan",
                }
            )
            return ExecutionReport(
                plan_id="original_plan",
                success=False,
                results={},
                errors=["dynamic_replan_requested"],
            )

        return ExecutionReport(
            plan_id="refined_plan_001",
            success=True,
            results={},
            errors=[],
        )

    mock_executor.execute_plan = AsyncMock(side_effect=execute_plan_side_effect)

    refined_plan = {
        "intent": "Inspect hazard source",
        "workflow_topology": "DAG",
        "plan_id": "refined_plan_001",
        "steps": [
            {
                "step_id": "s1",
                "type": "EXECUTION",
                "agent_role": "robot",
                "action": "VERIFY_OBJECT",
                "params": {"expected_class": "jerry_can"},
            }
        ],
    }

    mock_planner = MagicMock()
    mock_planner.refine_plan = MagicMock(return_value=SimpleNamespace(plan=refined_plan))

    server = OrchestratorServer(config=config, planner=mock_planner, executor=mock_executor)
    server.broadcast_to_dashboard = AsyncMock()
    server._send_actions_complete = AsyncMock()
    server._send_actions_failed = AsyncMock()

    original_plan = {
        "intent": "Explore the demo space",
        "workflow_topology": "DAG",
        "plan_id": "original_plan",
        "steps": [
            {
                "step_id": "scan1",
                "type": "EXECUTION",
                "agent_role": "robot",
                "action": "SCAN_AREA",
                "params": {"target": "office_tables"},
            }
        ],
    }

    await server._execute_plan_and_transition(original_plan)

    mock_executor.cancel.assert_called_once_with("dynamic_replan_requested")
    assert mock_executor.execute_plan.await_count == 2
    assert mock_executor.execute_plan.await_args_list[0].args[0] == original_plan
    assert "on_replan_requested" in mock_executor.execute_plan.await_args_list[0].kwargs
    assert mock_executor.execute_plan.await_args_list[1].args[0] == refined_plan

    mock_planner.refine_plan.assert_called_once()
    server.broadcast_to_dashboard.assert_awaited_once()
    dashboard_message = server.broadcast_to_dashboard.await_args.args[0]
    assert dashboard_message["type"] == "plan_update"
    assert dashboard_message["phase"] == "dynamic_replan"
    assert dashboard_message["trigger"]["type"] == "dynamic_replan_requested"

    server._send_actions_complete.assert_awaited_once()
    server._send_actions_failed.assert_not_awaited()
