from __future__ import annotations

import asyncio
from pathlib import Path

import pytest

from tests.acp.conftest import _create_acp_agent
from tests.conftest import build_test_vibe_config
from tests.stubs.fake_backend import FakeBackend
from vibe.acp.acp_agent_loop import VibeAcpAgentLoop
from vibe.acp.exceptions import InvalidRequestError, SessionNotFoundError
from vibe.core.agent_loop import AgentLoop
from vibe.core.config import ModelConfig
from vibe.core.trusted_folders import trusted_folders_manager


async def _system_prompt(acp_agent_loop: VibeAcpAgentLoop, session_id: str) -> str:
    session = acp_agent_loop.sessions[session_id]
    await session.agent_loop.wait_until_ready()
    return session.agent_loop.messages[0].content or ""


async def _wait_for_background_tasks(
    acp_agent_loop: VibeAcpAgentLoop, session_id: str
) -> None:
    session = acp_agent_loop.sessions[session_id]
    while session._tasks:
        await asyncio.gather(*list(session._tasks))


@pytest.fixture
def acp_agent_loop(
    backend: FakeBackend, monkeypatch: pytest.MonkeyPatch
) -> VibeAcpAgentLoop:
    config = build_test_vibe_config(
        active_model="devstral-latest",
        models=[
            ModelConfig(
                name="devstral-latest", provider="mistral", alias="devstral-latest"
            ),
            ModelConfig(
                name="devstral-small", provider="mistral", alias="devstral-small"
            ),
        ],
    )

    class PatchedAgentLoop(AgentLoop):
        def __init__(self, *args, **kwargs) -> None:
            super().__init__(*args, **{**kwargs, "backend": backend})
            self._base_config = config
            self.agent_manager.invalidate_config()

    monkeypatch.setattr("vibe.acp.acp_agent_loop.AgentLoop", PatchedAgentLoop)
    return _create_acp_agent()


class TestWorkspaceTrustExtMethods:
    @pytest.mark.asyncio
    async def test_workspace_trust_status_returns_details_even_after_decline(
        self, acp_agent_loop: VibeAcpAgentLoop, tmp_working_directory: Path
    ) -> None:
        (tmp_working_directory / "AGENTS.md").write_text(
            "Trust me later", encoding="utf-8"
        )
        trusted_folders_manager.add_untrusted(tmp_working_directory)

        response = await acp_agent_loop.ext_method(
            "trust/status", {"cwd": str(tmp_working_directory)}
        )

        assert response == {
            "trust_status": "untrusted",
            "details": {
                "cwd": str(tmp_working_directory.resolve()),
                "repoRoot": None,
                "ignoredFiles": ["AGENTS.md"],
                "availableDecisions": ["trust_cwd", "decline"],
            },
        }

    @pytest.mark.asyncio
    async def test_workspace_trust_decision_trusts_cwd_and_reloads_session(
        self, acp_agent_loop: VibeAcpAgentLoop, tmp_working_directory: Path
    ) -> None:
        (tmp_working_directory / "AGENTS.md").write_text(
            "Reloaded session instructions", encoding="utf-8"
        )

        session_response = await acp_agent_loop.new_session(
            cwd=str(tmp_working_directory), mcp_servers=[]
        )
        assert session_response.session_id is not None
        assert "Reloaded session instructions" not in await _system_prompt(
            acp_agent_loop, session_response.session_id
        )

        response = await acp_agent_loop.ext_method(
            "trust/decision",
            {
                "cwd": str(tmp_working_directory),
                "decision": "trust_cwd",
                "session_id": session_response.session_id,
            },
        )

        assert response == {"trust_status": "trusted", "details": None}
        normalized = str(tmp_working_directory.resolve())
        assert normalized not in trusted_folders_manager._session_trusted
        assert normalized in trusted_folders_manager._trusted
        await _wait_for_background_tasks(acp_agent_loop, session_response.session_id)
        assert "Reloaded session instructions" in await _system_prompt(
            acp_agent_loop, session_response.session_id
        )

    @pytest.mark.asyncio
    async def test_workspace_trust_decision_returns_before_reload_completes(
        self,
        acp_agent_loop: VibeAcpAgentLoop,
        tmp_working_directory: Path,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        (tmp_working_directory / "AGENTS.md").write_text(
            "Slow reload instructions", encoding="utf-8"
        )

        session_response = await acp_agent_loop.new_session(
            cwd=str(tmp_working_directory), mcp_servers=[]
        )
        assert session_response.session_id is not None

        reload_started = asyncio.Event()
        release_reload = asyncio.Event()

        async def slow_reload_session_config(session) -> None:
            reload_started.set()
            await release_reload.wait()

        monkeypatch.setattr(
            acp_agent_loop, "_reload_session_config", slow_reload_session_config
        )

        decision_task = asyncio.create_task(
            acp_agent_loop.ext_method(
                "trust/decision",
                {
                    "cwd": str(tmp_working_directory),
                    "decision": "trust_cwd",
                    "session_id": session_response.session_id,
                },
            )
        )

        try:
            await asyncio.wait_for(reload_started.wait(), timeout=1)
            await asyncio.sleep(0)

            assert decision_task.done()
            assert decision_task.result() == {
                "trust_status": "trusted",
                "details": None,
            }
        finally:
            release_reload.set()
            await decision_task
            await _wait_for_background_tasks(
                acp_agent_loop, session_response.session_id
            )

    @pytest.mark.asyncio
    async def test_workspace_trust_decision_rejects_unavailable_decision(
        self, acp_agent_loop: VibeAcpAgentLoop, tmp_working_directory: Path
    ) -> None:
        (tmp_working_directory / "AGENTS.md").write_text(
            "No repo decision here", encoding="utf-8"
        )

        with pytest.raises(InvalidRequestError):
            await acp_agent_loop.ext_method(
                "trust/decision",
                {"cwd": str(tmp_working_directory), "decision": "trust_repo"},
            )

        with pytest.raises(InvalidRequestError):
            await acp_agent_loop.ext_method(
                "trust/decision",
                {"cwd": str(tmp_working_directory), "decision": "trust_session"},
            )

        assert trusted_folders_manager.is_trusted(tmp_working_directory) is None

    @pytest.mark.asyncio
    async def test_workspace_trust_decision_rejects_unknown_session_id(
        self, acp_agent_loop: VibeAcpAgentLoop, tmp_working_directory: Path
    ) -> None:
        (tmp_working_directory / "AGENTS.md").write_text(
            "Unknown session", encoding="utf-8"
        )

        with pytest.raises(SessionNotFoundError):
            await acp_agent_loop.ext_method(
                "trust/decision",
                {
                    "cwd": str(tmp_working_directory),
                    "decision": "trust_cwd",
                    "session_id": "missing-session",
                },
            )

        assert trusted_folders_manager.is_trusted(tmp_working_directory) is None
