from __future__ import annotations

import asyncio
from pathlib import Path
from typing import Any
from unittest.mock import patch
from uuid import uuid4

from acp import (
    PROTOCOL_VERSION,
    InitializeRequest,
    NewSessionRequest,
    PromptRequest,
    RequestError,
)
from acp.schema import TextContentBlock
import pytest
from pytest import raises

from revibe.acp.acp_agent import VibeAcpAgent
from revibe.core.agent import Agent
from revibe.core.config import ModelConfig, VibeConfig
from revibe.core.types import Role
from tests.mock.utils import mock_llm_chunk
from tests.stubs.fake_backend import FakeBackend
from tests.stubs.fake_connection import FakeAgentSideConnection


@pytest.fixture
def backend() -> FakeBackend:
    backend = FakeBackend()
    return backend


@pytest.fixture
def acp_agent(backend: FakeBackend) -> VibeAcpAgent:
    config = VibeConfig(
        active_model="devstral-latest",
        models=[
            ModelConfig(
                name="devstral-latest", provider="mistral", alias="devstral-latest"
            )
        ],
    )

    class PatchedAgent(Agent):
        def __init__(self, *args, **kwargs) -> None:
            super().__init__(*args, **kwargs)
            self.backend = backend
            self.config = config

    patch("revibe.acp.acp_agent.VibeAgent", side_effect=PatchedAgent).start()

    vibe_acp_agent: VibeAcpAgent | None = None

    def _create_agent(connection: Any) -> VibeAcpAgent:
        nonlocal vibe_acp_agent
        vibe_acp_agent = VibeAcpAgent(connection)
        return vibe_acp_agent

    FakeAgentSideConnection(_create_agent)
    return vibe_acp_agent  # type: ignore[return-value]


class TestMultiSessionCore:
    @pytest.mark.asyncio
    async def test_different_sessions_use_different_agents(
        self, acp_agent: VibeAcpAgent
    ) -> None:
        await acp_agent.initialize(InitializeRequest(protocolVersion=PROTOCOL_VERSION))
        session1_response = await acp_agent.newSession(
            NewSessionRequest(cwd=str(Path.cwd()), mcpServers=[])
        )
        session1 = acp_agent.sessions[session1_response.sessionId]
        session2_response = await acp_agent.newSession(
            NewSessionRequest(cwd=str(Path.cwd()), mcpServers=[])
        )
        session2 = acp_agent.sessions[session2_response.sessionId]

        assert session1.id != session2.id
        # Each agent should be independent
        assert session1.agent is not session2.agent
        assert id(session1.agent) != id(session2.agent)

    @pytest.mark.asyncio
    async def test_error_on_nonexistent_session(self, acp_agent: VibeAcpAgent) -> None:
        await acp_agent.initialize(InitializeRequest(protocolVersion=PROTOCOL_VERSION))
        await acp_agent.newSession(
            NewSessionRequest(cwd=str(Path.cwd()), mcpServers=[])
        )

        fake_session_id = "fake-session-id-" + str(uuid4())

        with raises(RequestError) as exc_info:
            await acp_agent.prompt(
                PromptRequest(
                    sessionId=fake_session_id,
                    prompt=[TextContentBlock(type="text", text="Hello, world!")],
                )
            )

        assert isinstance(exc_info.value, RequestError)
        assert str(exc_info.value) == "Invalid params"

    @pytest.mark.asyncio
    async def test_simultaneous_message_processing(
        self, acp_agent: VibeAcpAgent, backend: FakeBackend
    ) -> None:
        await acp_agent.initialize(InitializeRequest(protocolVersion=PROTOCOL_VERSION))
        session1_response = await acp_agent.newSession(
            NewSessionRequest(cwd=str(Path.cwd()), mcpServers=[])
        )
        session1 = acp_agent.sessions[session1_response.sessionId]
        session2_response = await acp_agent.newSession(
            NewSessionRequest(cwd=str(Path.cwd()), mcpServers=[])
        )
        session2 = acp_agent.sessions[session2_response.sessionId]

        backend._streams = [
            [mock_llm_chunk(content="Response 1")],
            [mock_llm_chunk(content="Response 2")],
        ]

        async def run_session1():
            await acp_agent.prompt(
                PromptRequest(
                    sessionId=session1.id,
                    prompt=[TextContentBlock(type="text", text="Prompt for session 1")],
                )
            )

        async def run_session2():
            await acp_agent.prompt(
                PromptRequest(
                    sessionId=session2.id,
                    prompt=[TextContentBlock(type="text", text="Prompt for session 2")],
                )
            )

        await asyncio.gather(run_session1(), run_session2())

        user_message1 = next(
            (msg for msg in session1.agent.messages if msg.role == Role.user), None
        )
        assert user_message1 is not None
        assert user_message1.content == "Prompt for session 1"
        assistant_message1 = next(
            (msg for msg in session1.agent.messages if msg.role == Role.assistant), None
        )
        assert assistant_message1 is not None
        assert assistant_message1.content == "Response 1"
        user_message2 = next(
            (msg for msg in session2.agent.messages if msg.role == Role.user), None
        )
        assert user_message2 is not None
        assert user_message2.content == "Prompt for session 2"
        assistant_message2 = next(
            (msg for msg in session2.agent.messages if msg.role == Role.assistant), None
        )
        assert assistant_message2 is not None
        assert assistant_message2.content == "Response 2"
