"""Test that agents can handle multiple consecutive edits to the same message."""

from __future__ import annotations

from typing import TYPE_CHECKING
from unittest.mock import AsyncMock, MagicMock, patch

import nio
import pytest

from mindroom.bot import AgentBot
from mindroom.config.agent import AgentConfig
from mindroom.config.main import Config
from mindroom.config.models import ModelConfig, RouterConfig
from mindroom.handled_turns import TurnRecord
from mindroom.matrix.cache.thread_history_result import thread_history_result
from mindroom.matrix.users import AgentMatrixUser
from tests.conftest import (
    TEST_PASSWORD,
    bind_runtime_paths,
    drain_coalescing,
    install_runtime_cache_support,
    make_matrix_client_mock,
    replace_edit_regenerator_deps,
    runtime_paths_for,
    test_runtime_paths,
)

if TYPE_CHECKING:
    from pathlib import Path


def _delivery_resolution(response_event_id: str | None) -> str | None:
    """Build one test-side response result for edit-regeneration tests."""
    return response_event_id


@pytest.mark.asyncio
async def test_agent_regenerates_on_multiple_edits(tmp_path: Path) -> None:
    """Test that agents regenerate their response on each consecutive edit."""
    # Set up agent and bot
    agent_user = AgentMatrixUser(
        user_id="@mindroom_test:localhost",
        password=TEST_PASSWORD,
        display_name="TestAgent",
        agent_name="test",
    )

    config = bind_runtime_paths(
        Config(
            agents={"test": AgentConfig(display_name="TestAgent", rooms=["!test:localhost"])},
            teams={},
            room_models={},
            models={"default": ModelConfig(provider="ollama", id="test-model")},
            router=RouterConfig(model="default"),
            authorization={"default_room_access": True},
        ),
        test_runtime_paths(tmp_path),
    )

    bot = AgentBot(
        agent_user=agent_user,
        storage_path=tmp_path,
        rooms=["!test:localhost"],
        enable_streaming=False,
        config=config,
        runtime_paths=runtime_paths_for(config),
    )

    # Mock the orchestrator and client
    mock_orchestrator = MagicMock()
    mock_orchestrator.current_config = config
    bot.orchestrator = mock_orchestrator

    bot.client = make_matrix_client_mock(user_id="@mindroom_test:localhost")
    install_runtime_cache_support(bot)
    bot._conversation_cache.get_thread_history = AsyncMock(return_value=thread_history_result([], is_full_history=True))
    bot._conversation_cache.get_dispatch_thread_history = AsyncMock(
        return_value=thread_history_result([], is_full_history=True),
    )
    bot._conversation_cache.get_dispatch_thread_snapshot = AsyncMock(
        return_value=thread_history_result([], is_full_history=False),
    )
    bot._conversation_resolver.fetch_thread_history = AsyncMock(
        return_value=thread_history_result([], is_full_history=True),
    )

    # Mock room send to return a response event ID
    mock_send_response = MagicMock()
    mock_send_response.__class__ = nio.RoomSendResponse
    mock_send_response.event_id = "$response123"
    bot.client.room_send.return_value = mock_send_response

    # Mock room messages for thread history
    bot.client.room_messages = AsyncMock(
        return_value=nio.RoomMessagesResponse.from_dict(
            {"chunk": [], "start": "s1", "end": "e1"},
            room_id="!test:localhost",
        ),
    )

    # Set up room
    room = nio.MatrixRoom(room_id="!test:localhost", own_user_id=bot.client.user_id)

    # Original message from user
    original_event = nio.RoomMessageText.from_dict(
        {
            "content": {
                "body": "@mindroom_test What's 2+2?",
                "msgtype": "m.text",
                "m.mentions": {"user_ids": ["@mindroom_test:localhost"]},
            },
            "event_id": "$original123",
            "sender": "@user:localhost",
            "origin_server_ts": 1000000,
            "type": "m.room.message",
            "room_id": "!test:localhost",
        },
    )
    original_event.source = original_event.__dict__["source"]

    # Process original message with mocked AI response
    with patch("mindroom.response_runner.ai_response", AsyncMock(return_value="Original: 4")):
        await bot._on_message(room, original_event)
        await drain_coalescing(bot)

    # Verify bot responded
    assert bot.client.room_send.call_count == 2  # thinking + final
    bot._turn_store.record_turn(
        TurnRecord.create(["$original123"], response_event_id="$response123"),
    )

    # Reset mock
    bot.client.room_send.reset_mock()

    # First edit from user
    edit1_event = nio.RoomMessageText.from_dict(
        {
            "content": {
                "body": "* @mindroom_test What's 3+3?",
                "msgtype": "m.text",
                "m.mentions": {"user_ids": ["@mindroom_test:localhost"]},  # Mentions at top level
                "m.new_content": {
                    "body": "@mindroom_test What's 3+3?",
                    "msgtype": "m.text",
                    "m.mentions": {"user_ids": ["@mindroom_test:localhost"]},
                },
                "m.relates_to": {
                    "rel_type": "m.replace",
                    "event_id": "$original123",  # Points to original
                },
            },
            "event_id": "$edit1",
            "sender": "@user:localhost",
            "origin_server_ts": 1000001,
            "type": "m.room.message",
            "room_id": "!test:localhost",
        },
    )
    edit1_event.source = edit1_event.__dict__["source"]

    # Process first edit and verify regeneration happens through the shared response helper.
    mock_generate_response = AsyncMock(return_value=_delivery_resolution("$response123"))
    replace_edit_regenerator_deps(bot, generate_response=mock_generate_response)
    await bot._on_message(room, edit1_event)
    await drain_coalescing(bot)

    assert mock_generate_response.await_count == 1

    # Reset mock
    bot.client.room_send.reset_mock()

    # Second edit from user
    edit2_event = nio.RoomMessageText.from_dict(
        {
            "content": {
                "body": "* @mindroom_test What's 4+4?",
                "msgtype": "m.text",
                "m.mentions": {"user_ids": ["@mindroom_test:localhost"]},  # Mentions at top level
                "m.new_content": {
                    "body": "@mindroom_test What's 4+4?",
                    "msgtype": "m.text",
                    "m.mentions": {"user_ids": ["@mindroom_test:localhost"]},
                },
                "m.relates_to": {
                    "rel_type": "m.replace",
                    "event_id": "$original123",  # Still points to original!
                },
            },
            "event_id": "$edit2",
            "sender": "@user:localhost",
            "origin_server_ts": 1000002,
            "type": "m.room.message",
            "room_id": "!test:localhost",
        },
    )
    edit2_event.source = edit2_event.__dict__["source"]

    # Process second edit and verify regeneration happens again.
    mock_generate_response = AsyncMock(return_value=_delivery_resolution("$response123"))
    replace_edit_regenerator_deps(bot, generate_response=mock_generate_response)
    await bot._on_message(room, edit2_event)
    await drain_coalescing(bot)

    assert mock_generate_response.await_count == 1

    # Third edit from user
    edit3_event = nio.RoomMessageText.from_dict(
        {
            "content": {
                "body": "* @mindroom_test What's 5+5?",
                "msgtype": "m.text",
                "m.mentions": {"user_ids": ["@mindroom_test:localhost"]},  # Mentions at top level
                "m.new_content": {
                    "body": "@mindroom_test What's 5+5?",
                    "msgtype": "m.text",
                    "m.mentions": {"user_ids": ["@mindroom_test:localhost"]},
                },
                "m.relates_to": {
                    "rel_type": "m.replace",
                    "event_id": "$original123",  # Always points to original
                },
            },
            "event_id": "$edit3",
            "sender": "@user:localhost",
            "origin_server_ts": 1000003,
            "type": "m.room.message",
            "room_id": "!test:localhost",
        },
    )
    edit3_event.source = edit3_event.__dict__["source"]

    # Reset mock
    bot.client.room_send.reset_mock()

    # Process third edit and verify regeneration still happens.
    mock_generate_response = AsyncMock(return_value=_delivery_resolution("$response123"))
    replace_edit_regenerator_deps(bot, generate_response=mock_generate_response)
    await bot._on_message(room, edit3_event)
    await drain_coalescing(bot)

    assert mock_generate_response.await_count == 1
