"""End-to-end tests: uninstall → reinstall → verify CLI, skills, MCP registration, codex tool calls.

Requires:
  - RUN_E2E=1 environment variable
  - Valid Reddit cookies in ~/.mkterswingman/reddit-mcp/cookies.json
  - Valid auth token in ~/.mkterswingman/auth.json
  - codex CLI installed (for codex integration tests)

Usage:
  RUN_E2E=1 python3 -m pytest tests/test_e2e_codex.py -v
"""

from __future__ import annotations

import json
import os
import shutil
import subprocess
import sys
from importlib.metadata import version
from pathlib import Path

import pytest

pytestmark = pytest.mark.skipif(
    not os.environ.get("RUN_E2E"),
    reason="E2E tests require RUN_E2E=1 and real credentials",
)

PACKAGE_NAME = "5mghost-rover"
EXECUTABLE_NAME = "reddit-mcp"
PROJECT_ROOT = Path(__file__).resolve().parents[1]

# Skill installation targets (subset — these are the most common)
SKILL_DIRS = [
    Path.home() / ".claude-internal" / "skills" / "use-reddit-mcp",
    Path.home() / ".codex" / "skills" / "use-reddit-mcp",
]


def _run(cmd: list[str], *, timeout: int = 60, check: bool = True) -> subprocess.CompletedProcess[str]:
    return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, check=check)


def _extract_tool_json(output: str, required_key: str) -> dict:
    """Extract JSON from codex output, handling the nested {"result": "..."} wrapper.

    Codex wraps MCP tool results as {"result": "<escaped-json-string>"}, so we
    parse the outer JSON first, then unwrap .result if it's a JSON string.
    """
    stripped = output.strip()
    for candidate in [stripped, *_find_json_objects(stripped)]:
        try:
            data = json.loads(candidate, strict=False)
        except (json.JSONDecodeError, ValueError):
            continue
        if not isinstance(data, dict):
            continue
        # Unwrap {"result": "<json-string>"} wrapper from codex
        if "result" in data and isinstance(data["result"], str) and required_key not in data:
            try:
                data = json.loads(data["result"])
            except (json.JSONDecodeError, ValueError):
                pass
        if isinstance(data, dict) and required_key in data:
            return data
    raise AssertionError(f"No JSON with '{required_key}' found in codex output:\n{output[:500]}")


def _find_json_objects(text: str) -> list[str]:
    """Find potential JSON object substrings in text by matching balanced braces."""
    results: list[str] = []
    for i, ch in enumerate(text):
        if ch == "{":
            depth = 0
            for j in range(i, len(text)):
                if text[j] == "{":
                    depth += 1
                elif text[j] == "}":
                    depth -= 1
                    if depth == 0:
                        results.append(text[i : j + 1])
                        break
    return results


# ---------------------------------------------------------------------------
# Phase 1: Package install verification
# ---------------------------------------------------------------------------


class TestPackageInstall:
    """Uninstall, reinstall from local source, verify CLI and files."""

    def test_01_uninstall_existing_package(self) -> None:
        """Remove existing installation so we test from a clean state."""
        _run([sys.executable, "-m", "pip", "uninstall", PACKAGE_NAME, "-y"], check=False)
        # Verify uninstalled
        result = _run([sys.executable, "-m", "pip", "show", PACKAGE_NAME], check=False)
        assert result.returncode != 0, "Package should be uninstalled"

    def test_02_install_from_local_source(self) -> None:
        """Install the package from the local project directory."""
        result = _run(
            [sys.executable, "-m", "pip", "install", "-e", str(PROJECT_ROOT)],
            timeout=120,
        )
        assert result.returncode == 0, f"Install failed: {result.stderr}"

    def test_03_cli_version_outputs_correctly(self) -> None:
        """Verify the CLI prints the expected version."""
        result = _run([sys.executable, "-m", "reddit_mcp.cli", "version"])
        assert "reddit-mcp" in result.stdout
        assert version("5mghost-rover") in result.stdout

    def test_04_cli_help_contains_all_commands(self) -> None:
        """Help output lists all required commands."""
        result = _run([sys.executable, "-m", "reddit_mcp.cli", "help"])
        for cmd in ["serve", "setup", "setup-cookies", "update", "install-skills", "check", "uninstall", "version"]:
            assert cmd in result.stdout, f"Missing command in help: {cmd}"

    def test_05_cli_check_runs_without_crash(self) -> None:
        """reddit-mcp check should run and output status (may show errors if no token)."""
        result = _run([sys.executable, "-m", "reddit_mcp.cli", "check"], check=False)
        # Should not crash regardless of auth state
        assert result.returncode == 0
        assert "reddit-mcp" in result.stdout


# ---------------------------------------------------------------------------
# Phase 2: Skill and launcher verification
# ---------------------------------------------------------------------------


class TestSkillInstallation:
    """Verify bundled skill is installed to expected AI client directories."""

    def test_01_install_skills_command(self) -> None:
        """Running install-skills should succeed."""
        result = _run([sys.executable, "-m", "reddit_mcp.cli", "install-skills"])
        assert result.returncode == 0

    @pytest.mark.parametrize("skill_dir", SKILL_DIRS, ids=lambda d: d.parent.parent.name)
    def test_02_skill_file_exists(self, skill_dir: Path) -> None:
        """SKILL.md is present in expected AI client skill directories."""
        skill_file = skill_dir / "SKILL.md"
        if not skill_dir.parent.exists():
            pytest.skip(f"AI client dir {skill_dir.parent} not present on this machine")
        assert skill_file.exists(), f"SKILL.md missing at {skill_file}"
        content = skill_file.read_text("utf-8")
        assert "use-reddit-mcp" in content
        assert "reddit-mcp" in content

    def test_03_launcher_exists(self) -> None:
        """Launcher script should exist at ~/.mkterswingman/reddit-mcp/launcher.py."""
        launcher = Path.home() / ".mkterswingman" / "reddit-mcp" / "launcher.py"
        assert launcher.exists(), f"Launcher missing at {launcher}"
        content = launcher.read_text("utf-8")
        assert PACKAGE_NAME in content


# ---------------------------------------------------------------------------
# Phase 3: MCP registration verification
# ---------------------------------------------------------------------------


class TestMCPRegistration:
    """Verify reddit-mcp is registered in codex and claude configs."""

    def test_codex_config_has_reddit_mcp(self) -> None:
        """reddit-mcp should be in ~/.codex/config.toml [mcp_servers]."""
        config_path = Path.home() / ".codex" / "config.toml"
        if not config_path.exists():
            pytest.skip("codex config not found")
        content = config_path.read_text("utf-8")
        assert "reddit-mcp" in content


# ---------------------------------------------------------------------------
# Phase 4: Codex real MCP tool invocation
# ---------------------------------------------------------------------------


@pytest.mark.skipif(not shutil.which("codex"), reason="codex CLI not installed")
class TestCodexToolCalls:
    """Use codex exec to actually invoke reddit-mcp MCP tools and verify real results."""

    def test_01_get_subreddit_posts_via_codex(self) -> None:
        """Codex calls get_subreddit_posts and returns real Reddit data."""
        result = _run(
            [
                "codex", "exec",
                "Use the reddit-mcp MCP tool get_subreddit_posts to fetch 3 posts "
                "from r/python with sort=new and limit=3. "
                "Return ONLY the raw JSON tool result, nothing else. No commentary.",
                "-s", "danger-full-access",
            ],
            timeout=120,
        )
        assert result.returncode == 0, f"codex exec failed: {result.stderr}"

        data = _extract_tool_json(result.stdout, "posts")
        assert "posts" in data, f"Missing 'posts' key in response: {list(data.keys())}"
        posts = data["posts"]
        assert isinstance(posts, list)
        assert len(posts) > 0, "Expected at least 1 post"

        # Verify post structure
        post = posts[0]
        for field in ["id", "title", "subreddit"]:
            assert field in post, f"Post missing '{field}' field"
        assert post["subreddit"].lower() == "python"

    def test_02_get_subreddit_info_via_codex(self) -> None:
        """Codex calls get_subreddit_info and returns real subreddit metadata."""
        result = _run(
            [
                "codex", "exec",
                "Use the reddit-mcp MCP tool get_subreddit_info to get info about r/python. "
                "Return ONLY the raw JSON tool result, nothing else. No commentary.",
                "-s", "danger-full-access",
            ],
            timeout=120,
        )
        assert result.returncode == 0, f"codex exec failed: {result.stderr}"

        data = _extract_tool_json(result.stdout, "subscribers")
        assert "subscribers" in data
        assert isinstance(data["subscribers"], int)
        assert data["subscribers"] > 0
        assert data.get("name", "").lower() == "python"

    def test_03_check_cookies_via_codex(self) -> None:
        """Codex calls check_cookies to verify cookie health."""
        result = _run(
            [
                "codex", "exec",
                "Use the reddit-mcp MCP tool check_cookies. "
                "Return ONLY the raw JSON tool result, nothing else.",
                "-s", "danger-full-access",
            ],
            timeout=120,
        )
        assert result.returncode == 0, f"codex exec failed: {result.stderr}"

        data = _extract_tool_json(result.stdout, "valid")
        assert "valid" in data
        assert data["valid"] is True, f"Cookies not valid: {data}"

    def test_04_search_reddit_via_codex(self) -> None:
        """Codex calls search_reddit with a query."""
        result = _run(
            [
                "codex", "exec",
                "Use the reddit-mcp MCP tool search_reddit to search for 'python asyncio' "
                "with limit=3. Return ONLY the raw JSON tool result, nothing else.",
                "-s", "danger-full-access",
            ],
            timeout=120,
        )
        assert result.returncode == 0, f"codex exec failed: {result.stderr}"

        data = _extract_tool_json(result.stdout, "results")
        assert "results" in data
        assert len(data["results"]) > 0
