"""E2E MCP seam proofs with real socket/runtime boundaries.

Spec source: contract-alignment-integration.fix-seam-purity-tool-policy-blockers (B2).
These tests intentionally avoid mock seams and exercise live MCP runtime paths.
"""

from __future__ import annotations

import asyncio
import shutil
import socket
import uuid
from pathlib import Path
from typing import Generator

import pytest
from returns.result import Success

from anima.core.lifecycle import LifecycleEvent
from anima.core.schemas import JobSpec
from anima.shell.agent import AgentRunner
from anima.shell.mcp_agent import AgentMCPServer
from anima.shell.mcp_serve import ServeMCPServer
from anima.shell.state import create_agent_state, delete_agent_state, transition_state
from tests.integration_helpers import call_anima_status


async def _wait_for_path(path: Path, timeout: float = 3.0) -> None:
    """Poll for path existence within timeout window."""
    deadline = asyncio.get_running_loop().time() + timeout
    while asyncio.get_running_loop().time() < deadline:
        if path.exists():
            return
        await asyncio.sleep(0.02)
    raise AssertionError(f"Timed out waiting for path: {path}")


@pytest.fixture
def short_state_dir(monkeypatch: pytest.MonkeyPatch) -> Generator[Path, None, None]:
    """Provide short ANIMA_STATE path to satisfy Unix socket limits."""
    state_dir = Path(f"/tmp/anima_e2e_{uuid.uuid4().hex[:8]}")
    state_dir.mkdir(parents=True, exist_ok=True)
    monkeypatch.setenv("ANIMA_STATE", str(state_dir))
    try:
        yield state_dir
    finally:
        if state_dir.exists():
            shutil.rmtree(state_dir, ignore_errors=True)


@pytest.fixture
def free_port() -> int:
    """Allocate a free TCP port for serve-mode MCP tests."""
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.bind(("", 0))
        return int(sock.getsockname()[1])


@pytest.mark.asyncio
async def test_agent_mcp_socket_transport_status(short_state_dir: Path) -> None:
    """Per-agent MCP status is callable through the real Unix-socket transport."""
    _ = short_state_dir
    instance_id = "inst_e2e_mcp_socket"
    created = create_agent_state(
        instance_id,
        "persona-e2e",
        JobSpec(persona_ref="persona-e2e@test", instance_id=instance_id),
    )
    assert isinstance(created, Success)
    transition = transition_state(instance_id, LifecycleEvent.start)
    assert isinstance(transition, Success)

    runner = AgentRunner(instance_id=instance_id, task="e2e mcp socket test")
    server = AgentMCPServer(instance_id, runner)
    started = await server.start()
    assert isinstance(started, Success)

    try:
        socket_path = short_state_dir / "agents" / instance_id / "mcp.sock"
        await _wait_for_path(socket_path)

        status = await call_anima_status(socket_path)
        assert status["instance_id"] == instance_id
        assert status["state"] == "running"
        assert status["persona_name"] == "persona-e2e"
    finally:
        await server.stop()
        delete_agent_state(instance_id)


@pytest.mark.asyncio
async def test_serve_mcp_start_listen_stop_real_port(free_port: int) -> None:
    """Serve MCP binds a real TCP port and accepts a client connection."""
    server = ServeMCPServer(host="127.0.0.1", port=free_port)
    started = await server.start()
    assert isinstance(started, Success)

    try:
        await asyncio.sleep(0.2)
        reader, writer = await asyncio.wait_for(
            asyncio.open_connection("127.0.0.1", free_port),
            timeout=2.0,
        )
        _ = reader
        writer.close()
        await writer.wait_closed()
    finally:
        stopped = await server.stop()
        assert isinstance(stopped, Success)
