"""Pytest fixtures for snapshot testing.

These fixtures provide:
- Real git repositories
- Real database
- Real filesystem
- Mocked agent CLI (only external dependency)
- Standardized terminal size for consistent snapshots
- SVG snapshot extension for proper .svg file output

Note on pytest-xdist:
    Syrupy works with xdist for reading/writing snapshots. However, when running
    ``--snapshot-update`` with multiple workers, unused snapshot detection is disabled.
    To detect and delete unused snapshots, run with ``-n 0`` (sequential mode).

    Snapshot tests are grouped on the same xdist worker using ``xdist_group`` marker
    to minimize potential race conditions during snapshot writes.

Note on pytest-textual-snapshot compatibility:
    pytest-textual-snapshot 1.0.0 uses syrupy 4.x API (_file_extension class attr)
    which is incompatible with syrupy 5.x (file_extension property). We override
    the snap_compare fixture to use syrupy's built-in SVGImageSnapshotExtension.
"""

from __future__ import annotations

import re
from pathlib import Path, PurePath
from types import SimpleNamespace
from typing import TYPE_CHECKING, Any

import pytest
from syrupy.extensions.image import SVGImageSnapshotExtension

from kagan.app import KaganApp
from kagan.command_utils import clear_which_cache
from tests.helpers.git import init_git_repo_with_commit
from tests.helpers.mocks import MockAgent, MockAgentFactory, create_fake_tmux

if TYPE_CHECKING:
    from collections.abc import Awaitable, Callable, Iterable

    from _pytest.fixtures import FixtureRequest
    from syrupy.assertion import SnapshotAssertion
    from textual.app import App
    from textual.pilot import Pilot


__all__ = ["MockAgent", "MockAgentFactory", "create_fake_tmux"]


_create_fake_tmux = create_fake_tmux


def _normalize_svg(svg: str) -> str:
    """Strip unique terminal IDs generated by rich.Console.export_svg()."""
    return re.sub(r"\bterminal-\d+-([\w-]+)", r"terminal-\1", svg)


SNAPSHOT_TERMINAL_COLS = 120
SNAPSHOT_TERMINAL_ROWS = 40


@pytest.fixture(autouse=True)
def _mock_agent_gates_for_ci(monkeypatch: pytest.MonkeyPatch) -> None:
    """Bypass agent-availability and MCP-configuration gates on CI.

    On CI runners the ``claude`` (or other agent) CLI is not installed, so:

    1. ``AgentHealthServiceImpl._check_agent()`` marks the agent as unavailable
       and PlannerScreen never calls ``_start_planner()``.
    2. ``check_agent_installed()`` returns False, blocking PAIR session flows
       behind an AgentChoiceModal.
    3. ``is_global_mcp_configured()`` returns False (no MCP config files on CI),
       causing an ``McpInstallModal`` to block session flows.

    This fixture mocks the two ``shutil.which`` entry-points and the MCP config
    check so that all snapshot tests run identically on CI and locally.
    """
    clear_which_cache()
    monkeypatch.setattr(
        "kagan.services.agent_health.shutil.which",
        lambda _cmd, *_a, **_kw: "/usr/bin/mock",
    )
    monkeypatch.setattr(
        "kagan.agents.installer.shutil.which",
        lambda _cmd, *_a, **_kw: "/usr/bin/mock",
    )
    monkeypatch.setattr(
        "kagan.mcp.global_config.is_global_mcp_configured",
        lambda _agent: True,
    )


@pytest.fixture(autouse=True)
def _snapshot_color_env(monkeypatch: pytest.MonkeyPatch) -> None:
    """Stabilize snapshot rendering colors across the full test suite."""
    monkeypatch.delenv("NO_COLOR", raising=False)
    monkeypatch.setenv("TEXTUAL_COLOR_SYSTEM", "truecolor")
    monkeypatch.setenv("COLORTERM", "truecolor")


@pytest.fixture
def snap_compare(
    snapshot: SnapshotAssertion, request: FixtureRequest
) -> Callable[[str | PurePath | App[Any]], bool]:
    """Compare Textual app screenshots with stored SVG snapshots."""
    snapshot = snapshot.use_extension(SVGImageSnapshotExtension)

    def compare(
        app: str | PurePath | App[Any],
        press: Iterable[str] = (),
        terminal_size: tuple[int, int] = (80, 24),
        run_before: Callable[[Pilot], Awaitable[None] | None] | None = None,
    ) -> bool:
        """Compare current app screenshot with stored snapshot."""
        from textual._doc import take_svg_screenshot
        from textual._import_app import import_app
        from textual.app import App as TextualApp

        if isinstance(app, TextualApp):
            app_instance = app
        else:
            path = Path(app)
            if path.is_absolute():
                app_path = str(path.resolve())
            else:
                node_path = request.node.path.parent
                app_path = str((node_path / app).resolve())
            app_instance = import_app(app_path)

        actual_screenshot = take_svg_screenshot(
            app=app_instance,
            press=press,
            terminal_size=terminal_size,
            run_before=run_before,
        )

        normalized_screenshot = _normalize_svg(actual_screenshot)
        return snapshot == normalized_screenshot

    return compare


@pytest.fixture
async def snapshot_project(tmp_path: Path) -> SimpleNamespace:
    """Create a real project with git repo and kagan config for snapshot testing."""
    from kagan.adapters.db.repositories import RepoRepository, TaskRepository

    project = tmp_path / "snapshot_project"
    project.mkdir()

    await init_git_repo_with_commit(project)

    config_dir = tmp_path / "kagan-config"
    config_dir.mkdir()
    data_dir = tmp_path / "kagan-data"
    data_dir.mkdir()

    config_content = """# Kagan Snapshot Test Configuration
[general]
auto_review = false
default_base_branch = "main"
default_worker_agent = "claude"

[agents.claude]
identity = "claude.ai"
name = "Claude"
short_name = "claude"
run_command."*" = "echo mock-claude"
interactive_command."*" = "echo mock-claude-interactive"
active = true
"""
    config_path = config_dir / "config.toml"
    config_path.write_text(config_content)

    db_path = str(data_dir / "kagan.db")

    task_repo = TaskRepository(db_path, project_root=project)
    await task_repo.initialize()

    project_id = await task_repo.ensure_test_project("Snapshot Test Project")

    assert task_repo._session_factory is not None
    repo_repo = RepoRepository(task_repo._session_factory)
    repo, _ = await repo_repo.get_or_create(project, default_branch="main")
    if repo.id:
        await repo_repo.add_to_project(project_id, repo.id, is_primary=True)

    await task_repo.close()

    return SimpleNamespace(
        root=project,
        db=db_path,
        config=str(config_path),
    )


@pytest.fixture
def mock_acp_agent_factory() -> MockAgentFactory:
    """Factory that returns mock agents with controllable responses."""
    return MockAgentFactory()


@pytest.fixture
async def snapshot_app(
    snapshot_project: SimpleNamespace,
    mock_acp_agent_factory: MockAgentFactory,
    monkeypatch: pytest.MonkeyPatch,
) -> KaganApp:
    """Create KaganApp with real DB, real git, but mocked agent factory."""
    sessions: dict[str, dict[str, Any]] = {}
    fake_tmux = create_fake_tmux(sessions)
    monkeypatch.setattr("kagan.tmux.run_tmux", fake_tmux)
    monkeypatch.setattr("kagan.services.sessions.run_tmux", fake_tmux)

    app = KaganApp(
        db_path=snapshot_project.db,
        config_path=snapshot_project.config,
        project_root=snapshot_project.root,
        agent_factory=mock_acp_agent_factory,
    )

    return app


@pytest.fixture
def snapshot_terminal_size() -> tuple[int, int]:
    """Return standardized terminal size for snapshots (cols, rows)."""
    return (SNAPSHOT_TERMINAL_COLS, SNAPSHOT_TERMINAL_ROWS)


def pytest_collection_modifyitems(items: list[pytest.Item]) -> None:
    """Automatically group snapshot tests on the same xdist worker."""
    for item in items:
        if "tests/snapshots" in str(item.fspath) or "tests\\snapshots" in str(item.fspath):
            item.add_marker(pytest.mark.xdist_group(name="snapshots"))
