"""Tests for MCP mode — stdio and HTTP transports."""

import json
import subprocess
import sys
from pathlib import Path

import pytest

MCP_SERVER = str(Path(__file__).parent / "mcp_test_server.py")


class TestMCPStdio:
    """Integration tests using the stdio MCP test server."""

    def _run(self, *args, stdin_data=None) -> subprocess.CompletedProcess:
        cmd = [
            sys.executable,
            "-m",
            "mcp2cli",
            "--mcp-stdio",
            f"{sys.executable} {MCP_SERVER}",
            *args,
        ]
        return subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            input=stdin_data,
            timeout=30,
        )

    def test_list_tools(self):
        r = self._run("--list")
        assert r.returncode == 0
        assert "echo" in r.stdout
        assert "add-numbers" in r.stdout
        assert "list-items" in r.stdout

    def test_tool_help_shows_description(self):
        r = self._run("echo", "--help")
        assert r.returncode == 0
        assert "Echo back the input" in r.stdout

    def test_echo(self):
        r = self._run("echo", "--message", "hello world")
        assert r.returncode == 0
        assert "hello world" in r.stdout

    def test_add_numbers(self):
        r = self._run("add-numbers", "--a", "3", "--b", "7")
        assert r.returncode == 0
        assert "10" in r.stdout

    def test_list_items(self):
        r = self._run("list-items", "--path", "/tmp")
        assert r.returncode == 0
        data = json.loads(r.stdout)
        assert data["path"] == "/tmp"
        assert "items" in data

    def test_list_items_with_boolean(self):
        r = self._run("list-items", "--path", "/tmp", "--recursive")
        assert r.returncode == 0
        data = json.loads(r.stdout)
        assert data["recursive"] is True

    def test_echo_stdin(self):
        r = self._run("echo", "--stdin", stdin_data='{"message": "from stdin"}')
        assert r.returncode == 0
        assert "from stdin" in r.stdout

    def test_echo_stdin_invalid_json(self):
        r = self._run("echo", "--stdin", stdin_data='{"message":')
        assert r.returncode != 0
        assert "invalid JSON" in r.stderr

    def test_no_subcommand_shows_tools(self):
        r = self._run()
        assert r.returncode == 0
        assert "echo" in r.stdout

    def test_pretty_output(self):
        r = self._run("--pretty", "list-items", "--path", "/test")
        assert r.returncode == 0
        # Pretty output should be indented
        assert "  " in r.stdout

    def test_raw_output(self):
        r = self._run("--raw", "echo", "--message", "raw test")
        assert r.returncode == 0
        assert "raw test" in r.stdout

    def test_env_vars(self):
        """Test that --env flag is accepted (env vars passed to subprocess)."""
        r = self._run("--env", "TEST_VAR=hello", "echo", "--message", "test")
        assert r.returncode == 0

    # --- GH #14: --search filters tools ---

    def test_search_by_name(self):
        """--search filters tools by name."""
        r = self._run("--search", "echo")
        assert r.returncode == 0
        assert "echo" in r.stdout
        assert "add-numbers" not in r.stdout

    def test_search_by_description(self):
        """--search matches against description too."""
        r = self._run("--search", "directory")
        assert r.returncode == 0
        assert "list-items" in r.stdout
        assert "echo" not in r.stdout

    def test_search_case_insensitive(self):
        """--search is case-insensitive."""
        r = self._run("--search", "ECHO")
        assert r.returncode == 0
        assert "echo" in r.stdout

    def test_search_no_matches(self):
        """--search with no matches prints helpful message."""
        r = self._run("--search", "nonexistent_xyz")
        assert r.returncode == 0
        assert "No tools matching" in r.stdout

    # --- GH #15: global options must not shadow tool parameters ---

    def test_tool_param_not_shadowed_by_global_env(self):
        """Tool --env parameter must not be consumed by global --env."""
        r = self._run("deploy", "--env", "production")
        assert r.returncode == 0
        data = json.loads(r.stdout)
        assert data["env"] == "production"

    def test_tool_param_not_shadowed_by_global_refresh(self):
        """Tool --refresh must not be consumed by global --refresh."""
        r = self._run("deploy", "--env", "staging", "--refresh")
        assert r.returncode == 0
        data = json.loads(r.stdout)
        assert data["refresh"] is True

    def test_global_and_tool_env_coexist(self):
        """Global --env before subcommand + tool --env after subcommand."""
        r = self._run("--env", "X=1", "deploy", "--env", "production")
        assert r.returncode == 0
        data = json.loads(r.stdout)
        assert data["env"] == "production"

    def test_tool_caching(self, tmp_path, monkeypatch):
        """Run twice — second should use cached tool list."""
        import mcp2cli

        monkeypatch.setattr(mcp2cli, "CACHE_DIR", tmp_path / "cache")

        # First run fetches and caches
        r1 = self._run("echo", "--message", "first")
        assert r1.returncode == 0

        # Cached tools file should exist
        cache_files = (
            list((tmp_path / "cache").glob("*_tools.json"))
            if (tmp_path / "cache").exists()
            else []
        )
        # Cache may or may not exist depending on subprocess isolation
        # Just verify both runs succeed
        r2 = self._run("echo", "--message", "second")
        assert r2.returncode == 0

    # --- Resources ---

    def test_list_resources(self):
        r = self._run("--list-resources")
        assert r.returncode == 0
        data = json.loads(r.stdout)
        names = [d["name"] for d in data]
        assert "Test Document" in names

    def test_list_resource_templates(self):
        r = self._run("--list-resource-templates")
        assert r.returncode == 0
        data = json.loads(r.stdout)
        assert len(data) >= 1
        assert "uriTemplate" in data[0]

    def test_read_resource(self):
        r = self._run("--read-resource", "file:///test/doc.txt")
        assert r.returncode == 0
        assert "Hello from test document!" in r.stdout

    def test_read_resource_not_found(self):
        r = self._run("--read-resource", "file:///nonexistent")
        assert r.returncode != 0

    # --- Prompts ---

    def test_list_prompts(self):
        r = self._run("--list-prompts")
        assert r.returncode == 0
        data = json.loads(r.stdout)
        names = [d["name"] for d in data]
        assert "greeting" in names
        assert "summary" in names

    def test_get_prompt(self):
        r = self._run("--get-prompt", "greeting", "--prompt-arg", "name=Alice")
        assert r.returncode == 0
        data = json.loads(r.stdout)
        assert "messages" in data
        assert "Alice" in data["messages"][0]["content"]

    def test_get_prompt_no_args(self):
        r = self._run("--get-prompt", "greeting")
        assert r.returncode == 0
        data = json.loads(r.stdout)
        assert "messages" in data
        # Default name should be "World"
        assert "World" in data["messages"][0]["content"]


class TestMCPHTTP:
    """Tests for MCP HTTP transport.

    These use a subprocess-based MCP HTTP server started as a fixture.
    We test the tool listing and invocation via streamable HTTP / SSE.
    """

    @pytest.fixture(scope="class")
    def mcp_http_server(self):
        """Start an MCP HTTP server for testing.

        Uses the `mcp` package's built-in HTTP server capabilities.
        """
        server_script = Path(__file__).parent / "_mcp_http_server.py"
        if not server_script.exists():
            # Create a minimal MCP HTTP server script
            server_script.write_text(_MCP_HTTP_SERVER_SCRIPT)

        proc = subprocess.Popen(
            [sys.executable, str(server_script)],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
        )

        # Wait for server to be ready by reading the port from stdout
        import time

        port = None
        deadline = time.time() + 10
        while time.time() < deadline:
            line = proc.stdout.readline().strip()
            if line.startswith("PORT="):
                port = int(line.split("=")[1])
                break
            if proc.poll() is not None:
                stderr = proc.stderr.read()
                pytest.skip(f"MCP HTTP server failed to start: {stderr}")
                return

        if port is None:
            proc.kill()
            pytest.skip("MCP HTTP server did not report port in time")
            return

        url = f"http://127.0.0.1:{port}/sse"
        yield url
        proc.terminate()
        proc.wait(timeout=5)

    def _run(self, url, *args) -> subprocess.CompletedProcess:
        cmd = [
            sys.executable,
            "-m",
            "mcp2cli",
            "--mcp",
            url,
            *args,
        ]
        return subprocess.run(cmd, capture_output=True, text=True, timeout=30)

    def test_list_tools_http(self, mcp_http_server):
        r = self._run(mcp_http_server, "--list")
        assert r.returncode == 0
        assert "echo" in r.stdout

    def test_echo_http(self, mcp_http_server):
        r = self._run(mcp_http_server, "echo", "--message", "http test")
        assert r.returncode == 0
        assert "http test" in r.stdout

    def test_add_numbers_http(self, mcp_http_server):
        r = self._run(mcp_http_server, "add-numbers", "--a", "10", "--b", "20")
        assert r.returncode == 0
        assert "30" in r.stdout

    # --- Resources (HTTP) ---

    def test_list_resources_http(self, mcp_http_server):
        r = self._run(mcp_http_server, "--list-resources")
        assert r.returncode == 0
        data = json.loads(r.stdout)
        names = [d["name"] for d in data]
        assert "Test Document" in names

    def test_list_resource_templates_http(self, mcp_http_server):
        r = self._run(mcp_http_server, "--list-resource-templates")
        assert r.returncode == 0
        data = json.loads(r.stdout)
        assert len(data) >= 1

    def test_read_resource_http(self, mcp_http_server):
        r = self._run(mcp_http_server, "--read-resource", "file:///test/doc.txt")
        assert r.returncode == 0
        assert "Hello from test document!" in r.stdout

    # --- Prompts (HTTP) ---

    def test_list_prompts_http(self, mcp_http_server):
        r = self._run(mcp_http_server, "--list-prompts")
        assert r.returncode == 0
        data = json.loads(r.stdout)
        names = [d["name"] for d in data]
        assert "greeting" in names

    def test_get_prompt_http(self, mcp_http_server):
        r = self._run(
            mcp_http_server, "--get-prompt", "greeting", "--prompt-arg", "name=Bob"
        )
        assert r.returncode == 0
        data = json.loads(r.stdout)
        assert "Bob" in data["messages"][0]["content"]


class TestSessions:
    """Tests for persistent session support."""

    def test_session_lifecycle(self):
        """Start, list, and stop a session."""
        server = f"{sys.executable} {MCP_SERVER}"
        name = "test-lifecycle"

        # Start
        r = subprocess.run(
            [
                sys.executable,
                "-m",
                "mcp2cli",
                "--mcp-stdio",
                server,
                "--session-start",
                name,
            ],
            capture_output=True,
            text=True,
            timeout=30,
        )
        assert r.returncode == 0
        assert name in r.stdout

        try:
            # List
            r = subprocess.run(
                [sys.executable, "-m", "mcp2cli", "--session-list"],
                capture_output=True,
                text=True,
                timeout=10,
            )
            assert r.returncode == 0
            assert name in r.stdout
            assert "alive" in r.stdout

            # Tool call via session
            r = subprocess.run(
                [
                    sys.executable,
                    "-m",
                    "mcp2cli",
                    "--session",
                    name,
                    "echo",
                    "--message",
                    "via session",
                ],
                capture_output=True,
                text=True,
                timeout=10,
            )
            assert r.returncode == 0
            assert "via session" in r.stdout

            # List tools via session
            r = subprocess.run(
                [sys.executable, "-m", "mcp2cli", "--session", name, "--list"],
                capture_output=True,
                text=True,
                timeout=10,
            )
            assert r.returncode == 0
            assert "echo" in r.stdout

            # Resources via session
            r = subprocess.run(
                [
                    sys.executable,
                    "-m",
                    "mcp2cli",
                    "--session",
                    name,
                    "--list-resources",
                ],
                capture_output=True,
                text=True,
                timeout=10,
            )
            assert r.returncode == 0
            data = json.loads(r.stdout)
            assert any(d["name"] == "Test Document" for d in data)

            # Prompts via session
            r = subprocess.run(
                [sys.executable, "-m", "mcp2cli", "--session", name, "--list-prompts"],
                capture_output=True,
                text=True,
                timeout=10,
            )
            assert r.returncode == 0
            data = json.loads(r.stdout)
            assert any(d["name"] == "greeting" for d in data)

        finally:
            # Stop
            r = subprocess.run(
                [sys.executable, "-m", "mcp2cli", "--session-stop", name],
                capture_output=True,
                text=True,
                timeout=10,
            )
            assert r.returncode == 0

        # Verify stopped
        r = subprocess.run(
            [sys.executable, "-m", "mcp2cli", "--session-list"],
            capture_output=True,
            text=True,
            timeout=10,
        )
        assert name not in r.stdout or "dead" in r.stdout


_MCP_HTTP_SERVER_SCRIPT = ""  # Server script is now in _mcp_http_server.py
