from __future__ import annotations

from types import SimpleNamespace
from typing import Any, cast
from unittest.mock import AsyncMock

from acp import PROTOCOL_VERSION
from acp.schema import (
    AgentCapabilities,
    ClientCapabilities,
    Implementation,
    PromptCapabilities,
    SessionCapabilities,
    SessionCloseCapabilities,
    SessionForkCapabilities,
    SessionListCapabilities,
)
import pytest

from tests.conftest import build_test_vibe_config
from vibe.acp.acp_agent_loop import VibeAcpAgentLoop
from vibe.core.config import ProviderConfig
from vibe.core.types import Backend
from vibe.setup.onboarding.context import OnboardingContext

BROWSER_AUTH_NAME = "Sign in through Mistral AI Studio"
BROWSER_AUTH_DESCRIPTION = (
    "Sign into Mistral Vibe through your Mistral AI Studio account."
)


def build_mistral_provider() -> ProviderConfig:
    return ProviderConfig(
        name="mistral",
        api_base="https://api.mistral.ai/v1",
        api_key_env_var="MISTRAL_API_KEY",
        browser_auth_base_url="https://console.mistral.ai",
        browser_auth_api_base_url="https://console.mistral.ai/api",
        backend=Backend.MISTRAL,
    )


def build_acp_agent_loop(*, provider: ProviderConfig | None = None) -> VibeAcpAgentLoop:
    return VibeAcpAgentLoop(
        onboarding_context_loader=lambda: OnboardingContext(
            provider=provider or build_mistral_provider()
        )
    )


@pytest.fixture
def unauthenticated_env(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.delenv("MISTRAL_API_KEY", raising=False)


class TestACPInitialize:
    @pytest.mark.asyncio
    async def test_initialize(self, unauthenticated_env: None) -> None:
        acp_agent_loop = build_acp_agent_loop()
        response = await acp_agent_loop.initialize(protocol_version=PROTOCOL_VERSION)

        assert response.protocol_version == PROTOCOL_VERSION
        assert response.agent_capabilities == AgentCapabilities(
            load_session=True,
            prompt_capabilities=PromptCapabilities(
                audio=False, embedded_context=True, image=True
            ),
            session_capabilities=SessionCapabilities(
                close=SessionCloseCapabilities(),
                list=SessionListCapabilities(),
                fork=SessionForkCapabilities(),
            ),
        )
        assert response.agent_info == Implementation(
            name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.17.1"
        )

        assert response.auth_methods is not None
        assert len(response.auth_methods) == 1
        auth_method = response.auth_methods[0]
        assert auth_method.id == "browser-auth"
        assert auth_method.name == BROWSER_AUTH_NAME
        assert auth_method.description == BROWSER_AUTH_DESCRIPTION

    @pytest.mark.asyncio
    async def test_load_config_uses_client_info_title_for_vibe_code_project_name(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        config = build_test_vibe_config()
        monkeypatch.setattr(
            "vibe.acp.acp_agent_loop.VibeConfig.load", lambda *args, **kwargs: config
        )
        acp_agent_loop = build_acp_agent_loop()

        await acp_agent_loop.initialize(
            protocol_version=PROTOCOL_VERSION,
            client_info=Implementation(name="zed", title="Zed", version="0.999.0"),
        )

        assert acp_agent_loop._load_config().vibe_code_project_name == "Zed"

    @pytest.mark.asyncio
    async def test_load_config_preserves_explicit_vibe_code_project_name(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        config = build_test_vibe_config(vibe_code_project_name="Configured Project")
        monkeypatch.setattr(
            "vibe.acp.acp_agent_loop.VibeConfig.load", lambda *args, **kwargs: config
        )
        acp_agent_loop = build_acp_agent_loop()

        await acp_agent_loop.initialize(
            protocol_version=PROTOCOL_VERSION,
            client_info=Implementation(name="zed", title="Zed", version="0.999.0"),
        )

        assert (
            acp_agent_loop._load_config().vibe_code_project_name == "Configured Project"
        )

    @pytest.mark.asyncio
    @pytest.mark.parametrize(
        "reload_method_name", ["_reload_config", "_reload_session_config"]
    )
    async def test_reload_config_uses_client_info_title_for_vibe_code_project_name(
        self, monkeypatch: pytest.MonkeyPatch, reload_method_name: str
    ) -> None:
        config = build_test_vibe_config()
        monkeypatch.setattr(
            "vibe.acp.acp_agent_loop.VibeConfig.load", lambda *args, **kwargs: config
        )
        acp_agent_loop = build_acp_agent_loop()
        agent_loop = SimpleNamespace(
            config=SimpleNamespace(tool_paths=[]),
            reload_with_initial_messages=AsyncMock(),
        )
        session = cast(Any, SimpleNamespace(agent_loop=agent_loop))

        await acp_agent_loop.initialize(
            protocol_version=PROTOCOL_VERSION,
            client_info=Implementation(name="zed", title="Zed", version="0.999.0"),
        )
        await getattr(acp_agent_loop, reload_method_name)(session)

        agent_loop.reload_with_initial_messages.assert_awaited_once()
        reloaded_config = agent_loop.reload_with_initial_messages.await_args.kwargs[
            "base_config"
        ]
        assert reloaded_config.vibe_code_project_name == "Zed"

    @pytest.mark.asyncio
    async def test_initialize_with_terminal_auth(
        self, unauthenticated_env: None
    ) -> None:
        """Test initialize with terminal-auth capabilities to check it was included."""
        acp_agent_loop = build_acp_agent_loop()
        client_capabilities = ClientCapabilities(field_meta={"terminal-auth": True})
        response = await acp_agent_loop.initialize(
            protocol_version=PROTOCOL_VERSION, client_capabilities=client_capabilities
        )

        assert response.protocol_version == PROTOCOL_VERSION
        assert response.agent_capabilities == AgentCapabilities(
            load_session=True,
            prompt_capabilities=PromptCapabilities(
                audio=False, embedded_context=True, image=True
            ),
            session_capabilities=SessionCapabilities(
                close=SessionCloseCapabilities(),
                list=SessionListCapabilities(),
                fork=SessionForkCapabilities(),
            ),
        )
        assert response.agent_info == Implementation(
            name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.17.1"
        )

        assert response.auth_methods is not None
        assert len(response.auth_methods) == 2

        browser_auth_method = response.auth_methods[0]
        assert browser_auth_method.id == "browser-auth"
        assert browser_auth_method.name == BROWSER_AUTH_NAME
        assert browser_auth_method.description == BROWSER_AUTH_DESCRIPTION

        auth_method = response.auth_methods[1]
        assert auth_method.id == "vibe-setup"
        assert auth_method.name == "Register your API Key"
        assert auth_method.description == "Register your API Key inside Mistral Vibe"
        assert auth_method.args is not None
        assert auth_method.args[-1:] == ["--setup"]
        assert auth_method.field_meta is not None
        assert "terminal-auth" in auth_method.field_meta
        terminal_auth_meta = auth_method.field_meta["terminal-auth"]
        assert "command" in terminal_auth_meta
        assert "args" in terminal_auth_meta
        assert terminal_auth_meta["args"][-1:] == ["--setup"]
        assert terminal_auth_meta["label"] == "Mistral Vibe Setup"

    @pytest.mark.asyncio
    async def test_initialize_with_delegated_browser_auth(
        self, unauthenticated_env: None
    ) -> None:
        acp_agent_loop = build_acp_agent_loop()
        client_capabilities = ClientCapabilities(
            field_meta={"browser-auth-delegated": True}
        )
        response = await acp_agent_loop.initialize(
            protocol_version=PROTOCOL_VERSION, client_capabilities=client_capabilities
        )

        assert response.auth_methods is not None
        assert len(response.auth_methods) == 2

        browser_auth_method = response.auth_methods[0]
        assert browser_auth_method.id == "browser-auth"
        assert browser_auth_method.name == BROWSER_AUTH_NAME
        assert browser_auth_method.description == BROWSER_AUTH_DESCRIPTION

        delegated_browser_auth_method = response.auth_methods[1]
        assert delegated_browser_auth_method.id == "browser-auth-delegated"
        assert delegated_browser_auth_method.name == BROWSER_AUTH_NAME
        assert delegated_browser_auth_method.description == BROWSER_AUTH_DESCRIPTION

    @pytest.mark.asyncio
    async def test_initialize_omits_browser_auth_when_provider_unsupported(
        self,
    ) -> None:
        acp_agent_loop = build_acp_agent_loop(
            provider=ProviderConfig(
                name="llamacpp",
                api_base="http://127.0.0.1:8080/v1",
                api_key_env_var="LLAMACPP_API_KEY",
                backend=Backend.GENERIC,
            )
        )

        response = await acp_agent_loop.initialize(protocol_version=PROTOCOL_VERSION)

        assert response.auth_methods == []

    @pytest.mark.asyncio
    async def test_initialize_omits_auth_methods_for_authenticated_jetbrains_client(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        monkeypatch.setenv("MISTRAL_API_KEY", "test-api-key")
        acp_agent_loop = build_acp_agent_loop()
        client_capabilities = ClientCapabilities(
            field_meta={"terminal-auth": True, "browser-auth-delegated": True}
        )
        client_info = Implementation(name="JetBrains.PyCharm", version="2026.1.2")

        response = await acp_agent_loop.initialize(
            protocol_version=PROTOCOL_VERSION,
            client_capabilities=client_capabilities,
            client_info=client_info,
        )

        assert response.auth_methods == []

    @pytest.mark.asyncio
    async def test_initialize_keeps_auth_methods_for_authenticated_non_jetbrains_client(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        monkeypatch.setenv("MISTRAL_API_KEY", "test-api-key")
        acp_agent_loop = build_acp_agent_loop()
        client_capabilities = ClientCapabilities(field_meta={"terminal-auth": True})
        client_info = Implementation(name="zed", version="0.999.0")

        response = await acp_agent_loop.initialize(
            protocol_version=PROTOCOL_VERSION,
            client_capabilities=client_capabilities,
            client_info=client_info,
        )

        assert response.auth_methods is not None
        assert {method.id for method in response.auth_methods} == {
            "browser-auth",
            "vibe-setup",
        }
