from __future__ import annotations

from types import SimpleNamespace
from unittest.mock import AsyncMock, Mock

import pytest

from adapter.matrix.bot import MatrixBot, build_runtime
from adapter.matrix.handlers import register_matrix_handlers
from adapter.matrix.handlers.context_commands import (
    make_handle_context,
    make_handle_load,
    make_handle_reset,
    make_handle_save,
)
from adapter.matrix.store import (
    get_load_pending,
    set_load_pending,
    set_room_meta,
)
from core.protocol import IncomingCommand, OutgoingMessage
from core.store import InMemoryStore
from sdk.interface import MessageResponse
from sdk.mock import MockPlatformClient
from sdk.prototype_state import PrototypeStateStore


class MatrixCommandPlatform(MockPlatformClient):
    def __init__(self) -> None:
        super().__init__()
        self._prototype_state = PrototypeStateStore()
        self._agent_api = object()
        self.disconnect_chat = AsyncMock()
        self.send_message = AsyncMock(
            return_value=MessageResponse(
                message_id="msg-1",
                response="ok",
                tokens_used=0,
                finished=True,
            )
        )


@pytest.fixture(autouse=True)
def clear_matrix_registry_env(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.delenv("MATRIX_AGENT_REGISTRY_PATH", raising=False)
    monkeypatch.delenv("MATRIX_PLATFORM_BACKEND", raising=False)


@pytest.mark.asyncio
async def test_save_command_auto_name_records_session():
    platform = MatrixCommandPlatform()
    store = InMemoryStore()
    await set_room_meta(
        store,
        "!room:example.org",
        {"chat_id": "C1", "matrix_user_id": "u1", "platform_chat_id": "41"},
    )
    handler = make_handle_save(
        agent_api=platform._agent_api,
        store=store,
        prototype_state=platform._prototype_state,
    )
    event = IncomingCommand(
        user_id="u1",
        platform="matrix",
        chat_id="!room:example.org",
        command="save",
        args=[],
    )

    result = await handler(event, None, platform, None, None)

    assert len(result) == 1
    assert isinstance(result[0], OutgoingMessage)
    assert "Запрос на сохранение отправлен агенту" in result[0].text
    sessions = await platform._prototype_state.list_saved_sessions("u1")
    assert len(sessions) == 1
    assert sessions[0]["name"].startswith("context-")
    assert sessions[0]["source_context_id"] == "41"


@pytest.mark.asyncio
async def test_save_command_with_name_uses_given_name():
    platform = MatrixCommandPlatform()
    store = InMemoryStore()
    await set_room_meta(
        store,
        "!room:example.org",
        {"chat_id": "C1", "matrix_user_id": "u1", "platform_chat_id": "41"},
    )
    handler = make_handle_save(
        agent_api=platform._agent_api,
        store=store,
        prototype_state=platform._prototype_state,
    )
    event = IncomingCommand(
        user_id="u1",
        platform="matrix",
        chat_id="!room:example.org",
        command="save",
        args=["my-session"],
    )

    await handler(event, None, platform, None, None)

    sessions = await platform._prototype_state.list_saved_sessions("u1")
    assert [session["name"] for session in sessions] == ["my-session"]


@pytest.mark.asyncio
async def test_load_command_shows_numbered_list_and_sets_pending():
    platform = MatrixCommandPlatform()
    runtime = build_runtime(platform=platform)
    await runtime.chat_mgr.get_or_create(
        user_id="u1",
        chat_id="C1",
        platform="matrix",
        surface_ref="!room:example.org",
        name="Chat 1",
    )
    await platform._prototype_state.add_saved_session("u1", "session-a")
    await platform._prototype_state.add_saved_session("u1", "session-b")

    handler = make_handle_load(store=runtime.store, prototype_state=platform._prototype_state)
    event = IncomingCommand(user_id="u1", platform="matrix", chat_id="C1", command="load", args=[])

    result = await handler(
        event,
        runtime.auth_mgr,
        platform,
        runtime.chat_mgr,
        runtime.settings_mgr,
    )

    assert "1. session-a" in result[0].text
    assert "2. session-b" in result[0].text
    pending = await get_load_pending(runtime.store, "u1", "!room:example.org")
    assert pending is not None
    assert [session["name"] for session in pending["saves"]] == ["session-a", "session-b"]


@pytest.mark.asyncio
async def test_load_command_without_saved_sessions_reports_empty():
    platform = MatrixCommandPlatform()
    store = InMemoryStore()
    handler = make_handle_load(store=store, prototype_state=platform._prototype_state)
    event = IncomingCommand(user_id="u1", platform="matrix", chat_id="C1", command="load", args=[])

    result = await handler(event, None, platform, None, None)

    assert "Нет сохранённых сессий" in result[0].text


@pytest.mark.asyncio
async def test_reset_command_assigns_new_platform_chat_id():
    from adapter.matrix.store import get_platform_chat_id, set_room_meta
    from sdk.prototype_state import PrototypeStateStore

    prototype_state = PrototypeStateStore()
    platform = MatrixCommandPlatform()
    runtime = build_runtime(platform=platform)
    store = runtime.store

    await set_room_meta(store, "!room:example.org", {"platform_chat_id": "7"})

    handler = make_handle_reset(store=store, prototype_state=prototype_state)
    event = IncomingCommand(
        user_id="u1",
        platform="matrix",
        chat_id="!room:example.org",
        command="reset",
        args=[],
    )

    result = await handler(
        event,
        runtime.auth_mgr,
        platform,
        runtime.chat_mgr,
        runtime.settings_mgr,
    )

    new_id = await get_platform_chat_id(store, "!room:example.org")
    assert new_id != "7"
    assert new_id == "1"
    assert "сброшен" in result[0].text.lower()


@pytest.mark.asyncio
async def test_clear_command_rotates_only_current_room_and_disconnects_old_upstream_chat():
    from adapter.matrix.store import get_platform_chat_id

    platform = MatrixCommandPlatform()
    runtime = build_runtime(platform=platform)
    await runtime.chat_mgr.get_or_create(
        user_id="u1",
        chat_id="C1",
        platform="matrix",
        surface_ref="!room-a:example.org",
        name="Chat A",
    )
    await runtime.chat_mgr.get_or_create(
        user_id="u1",
        chat_id="C2",
        platform="matrix",
        surface_ref="!room-b:example.org",
        name="Chat B",
    )
    await set_room_meta(
        runtime.store,
        "!room-a:example.org",
        {"chat_id": "C1", "matrix_user_id": "u1", "platform_chat_id": "41"},
    )
    await set_room_meta(
        runtime.store,
        "!room-b:example.org",
        {"chat_id": "C2", "matrix_user_id": "u1", "platform_chat_id": "99"},
    )

    handler = make_handle_reset(store=runtime.store, prototype_state=platform._prototype_state)
    event = IncomingCommand(
        user_id="u1",
        platform="matrix",
        chat_id="C1",
        command="clear",
        args=[],
    )

    result = await handler(
        event,
        runtime.auth_mgr,
        platform,
        runtime.chat_mgr,
        runtime.settings_mgr,
    )

    room_a_chat_id = await get_platform_chat_id(runtime.store, "!room-a:example.org")
    room_b_chat_id = await get_platform_chat_id(runtime.store, "!room-b:example.org")
    assert room_a_chat_id == "1"
    assert room_a_chat_id != "41"
    assert room_b_chat_id == "99"
    platform.disconnect_chat.assert_awaited_once_with("41")
    assert "сброшен" in result[0].text.lower()


def test_register_matrix_handlers_exposes_clear_and_optional_reset_alias():
    dispatcher = SimpleNamespace(register=Mock())

    register_matrix_handlers(
        dispatcher,
        client=object(),
        store=object(),
        registry=None,
        prototype_state=PrototypeStateStore(),
    )

    clear_calls = [
        call
        for call in dispatcher.register.call_args_list
        if call.args[:2] == (IncomingCommand, "clear")
    ]
    reset_calls = [
        call
        for call in dispatcher.register.call_args_list
        if call.args[:2] == (IncomingCommand, "reset")
    ]
    assert clear_calls
    assert len(reset_calls) <= 1


@pytest.mark.asyncio
async def test_context_command_shows_current_snapshot():
    platform = MatrixCommandPlatform()
    runtime = build_runtime(platform=platform)
    await runtime.chat_mgr.get_or_create(
        user_id="u1",
        chat_id="C1",
        platform="matrix",
        surface_ref="!room:example.org",
        name="Chat 1",
    )
    await set_room_meta(
        runtime.store,
        "!room:example.org",
        {"chat_id": "C1", "matrix_user_id": "u1", "platform_chat_id": "41"},
    )
    await platform._prototype_state.set_current_session("41", "session-a")
    await platform._prototype_state.set_last_tokens_used("41", 99)
    await platform._prototype_state.add_saved_session("u1", "session-a")
    handler = make_handle_context(store=runtime.store, prototype_state=platform._prototype_state)
    event = IncomingCommand(
        user_id="u1",
        platform="matrix",
        chat_id="C1",
        command="context",
        args=[],
    )

    result = await handler(
        event,
        runtime.auth_mgr,
        platform,
        runtime.chat_mgr,
        runtime.settings_mgr,
    )

    assert "Контекст чата: 41" in result[0].text
    assert "Сессия: session-a" in result[0].text
    assert "Токены (последний ответ): 99" in result[0].text
    assert "session-a" in result[0].text


@pytest.mark.asyncio
async def test_bot_intercepts_numeric_load_selection():
    platform = MatrixCommandPlatform()
    runtime = build_runtime(platform=platform)
    await set_room_meta(
        runtime.store,
        "!room:example.org",
        {
            "chat_id": "C1",
            "matrix_user_id": "@alice:example.org",
            "platform_chat_id": "41",
        },
    )
    client = SimpleNamespace(
        user_id="@bot:example.org",
        room_send=AsyncMock(),
    )
    bot = MatrixBot(client, runtime)
    await set_load_pending(
        runtime.store,
        "@alice:example.org",
        "!room:example.org",
        {"saves": [{"name": "session-a", "created_at": "2026-04-17T00:00:00+00:00"}]},
    )
    room = SimpleNamespace(room_id="!room:example.org")
    event = SimpleNamespace(sender="@alice:example.org", body="1")

    await bot.on_room_message(room, event)

    platform.send_message.assert_awaited_once()
    assert await platform._prototype_state.get_current_session("41") == "session-a"
    assert await platform._prototype_state.get_current_session("C1") == "session-a"
    client.room_send.assert_awaited_once_with(
        "!room:example.org",
        "m.room.message",
        {"msgtype": "m.text", "body": "Запрос на загрузку отправлен агенту: session-a"},
    )
