import asyncio
import json
import sys
from unittest.mock import AsyncMock

import pytest

from control_backend.agents.bdi.bdi_program_manager import BDIProgramManager
from control_backend.core.agent_system import InternalMessage
from control_backend.schemas.belief_message import BeliefMessage
from control_backend.schemas.program import Program

# Fix Windows Proactor loop for zmq
if sys.platform.startswith("win"):
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())


def make_valid_program_json(norm="N1", goal="G1"):
    return json.dumps(
        {
            "phases": [
                {
                    "id": "phase1",
                    "label": "Phase 1",
                    "triggers": [],
                    "norms": [{"id": "n1", "label": "Norm 1", "norm": norm}],
                    "goals": [
                        {"id": "g1", "label": "Goal 1", "description": goal, "achieved": False}
                    ],
                }
            ]
        }
    )


@pytest.mark.asyncio
async def test_send_to_bdi():
    manager = BDIProgramManager(name="program_manager_test")
    manager.send = AsyncMock()

    program = Program.model_validate_json(make_valid_program_json())
    await manager._send_to_bdi(program)

    assert manager.send.await_count == 1
    msg: InternalMessage = manager.send.await_args[0][0]
    assert msg.thread == "beliefs"

    beliefs = BeliefMessage.model_validate_json(msg.body)
    names = {b.name: b.arguments for b in beliefs.beliefs}

    assert "norms" in names and names["norms"] == ["N1"]
    assert "goals" in names and names["goals"] == ["G1"]


@pytest.mark.asyncio
async def test_receive_programs_valid_and_invalid():
    sub = AsyncMock()
    sub.recv_multipart.side_effect = [
        (b"program", b"{bad json"),
        (b"program", make_valid_program_json().encode()),
    ]

    manager = BDIProgramManager(name="program_manager_test")
    manager.sub_socket = sub
    manager._send_to_bdi = AsyncMock()
    manager._send_clear_llm_history = AsyncMock()

    try:
        # Will give StopAsyncIteration when the predefined `sub.recv_multipart` side-effects run out
        await manager._receive_programs()
    except StopAsyncIteration:
        pass

    # Only valid Program should have triggered _send_to_bdi
    assert manager._send_to_bdi.await_count == 1
    forwarded: Program = manager._send_to_bdi.await_args[0][0]
    assert forwarded.phases[0].norms[0].norm == "N1"
    assert forwarded.phases[0].goals[0].description == "G1"

    # Verify history clear was triggered
    assert manager._send_clear_llm_history.await_count == 1


@pytest.mark.asyncio
async def test_send_clear_llm_history(mock_settings):
    # Ensure the mock returns a string for the agent name (just like in your LLM tests)
    mock_settings.agent_settings.llm_agent_name = "llm_agent"

    manager = BDIProgramManager(name="program_manager_test")
    manager.send = AsyncMock()

    await manager._send_clear_llm_history()

    assert manager.send.await_count == 1
    msg: InternalMessage = manager.send.await_args[0][0]

    # Verify the content and recipient
    assert msg.body == "clear_history"
    assert msg.to == "llm_agent"
