#!/usr/bin/env python3
"""
Test script to verify arcade-mcp installation from source.

This script:
1. Installs arcade-mcp from source using uv
2. Runs basic CLI commands to verify functionality
3. Tests cross-platform compatibility (file locking with portalocker)
"""

import os
import shutil
import subprocess
import sys
from pathlib import Path

# Ensure UTF-8 encoding for cross-platform compatibility (especially Windows)
if sys.platform == "win32":
    # Set UTF-8 encoding for Windows console
    if hasattr(sys.stdout, "reconfigure"):
        sys.stdout.reconfigure(encoding="utf-8")
    if hasattr(sys.stderr, "reconfigure"):
        sys.stderr.reconfigure(encoding="utf-8")
    # Set environment variable for subprocesses
    os.environ["PYTHONIOENCODING"] = "utf-8"


class TestRunner:
    """Organizes and runs installation tests for arcade-mcp."""

    def __init__(self, project_root: Path):
        """Initialize test runner with project root."""
        self.project_root = project_root
        self.arcade_cmd = self._find_arcade_command()
        self.test_results: list[tuple[str, bool]] = []

    def _find_arcade_command(self) -> list[str]:
        """Find the arcade command (either direct or via uv run).

        When using uv run from temp dirs (e.g. configure tests), use
        ``--project`` instead of ``--directory`` so uv resolves the project
        environment without changing the subprocess working directory.
        """
        if shutil.which("arcade"):
            return ["arcade"]
        return ["uv", "run", "--project", str(self.project_root), "arcade"]

    def run_command(
        self,
        cmd: list[str],
        description: str,
        required: bool = True,
        cwd: Path | None = None,
        input_text: str | None = None,
    ) -> tuple[bool, str]:
        """Run a command and return success status and output."""
        print(f"\n{'=' * 60}")
        print(f"Testing: {description}")
        print(f"Command: {' '.join(cmd)}")
        print(f"{'=' * 60}")

        # Ensure UTF-8 encoding for cross-platform compatibility (especially Windows)
        env = os.environ.copy()
        env["PYTHONIOENCODING"] = "utf-8"

        try:
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                check=True,
                timeout=60,
                env=env,
                cwd=str(cwd) if cwd else None,
                input=input_text,
                encoding="utf-8",
                errors="replace",
            )
        except subprocess.TimeoutExpired:
            print(f"❌ Timeout: {description}")
            self.test_results.append((description, False))
            return False, "Command timed out"
        except subprocess.CalledProcessError as e:
            print(f"❌ Failed: {description}")
            print(f"Return code: {e.returncode}")
            if e.stdout:
                print(f"Stdout:\n{e.stdout}")
            if e.stderr:
                print(f"Stderr:\n{e.stderr}")
            self.test_results.append((description, False))
            if required:
                return False, e.stderr or e.stdout or ""
            return False, e.stderr or e.stdout or ""
        except Exception as e:
            print(f"❌ Error: {description}")
            print(f"Exception: {e}")
            self.test_results.append((description, False))
            if required:
                return False, str(e)
            return False, str(e)
        else:
            print(f"✅ Success: {description}")
            if result.stdout:
                print(f"Output:\n{result.stdout}")
            self.test_results.append((description, True))
            output = result.stdout
            return True, output

    def check_prerequisites(self) -> bool:
        """Check that required tools are available."""
        print("\n" + "=" * 60)
        print("Prerequisites Check")
        print("=" * 60)

        success, _ = self.run_command(["uv", "--version"], "Check uv availability", required=True)
        return success

    def _sync_dependencies_command(
        self,
        *,
        current_test: str | None = None,
    ) -> list[str]:
        """Build the `uv sync` command for the current runtime context.

        When this suite runs under pytest, avoid reinstalling pytest itself.
        This prevents mutating the active test runner environment and also
        avoids `pytest.exe` file-lock failures on Windows.
        """
        runtime_test = (
            current_test if current_test is not None else os.environ.get("PYTEST_CURRENT_TEST")
        )

        command = ["uv", "sync", "--dev"]
        if runtime_test:
            command.extend(["--inexact", "--no-install-package", "pytest"])
        return command

    def install_package(self) -> bool:
        """Install arcade-mcp from source."""
        print("\n" + "=" * 60)
        print("Installation Phase")
        print("=" * 60)

        # Sync dependencies
        sync_success, _ = self.run_command(
            self._sync_dependencies_command(),
            "Sync dependencies with uv",
            required=True,
        )
        if not sync_success:
            return False

        # Install package in editable mode
        install_success, _ = self.run_command(
            ["uv", "pip", "install", "-e", str(self.project_root)],
            "Install arcade-mcp from source (editable mode)",
            required=True,
        )
        return install_success

    def test_cli_availability(self) -> bool:
        """Test that the CLI is available and working."""
        print("\n" + "=" * 60)
        print("CLI Functionality Tests")
        print("=" * 60)

        # Test --help
        help_success, _ = self.run_command(
            [*self.arcade_cmd, "--help"],
            "Verify arcade CLI is available (--help)",
            required=True,
        )
        if not help_success:
            return False

        # Test --version (optional, might not exist)
        self.run_command(
            [*self.arcade_cmd, "--version"],
            "Check arcade version",
            required=False,
        )

        # Test whoami (might fail if not logged in, but shouldn't crash)
        self.run_command(
            [*self.arcade_cmd, "whoami"],
            "Test whoami command (no auth required)",
            required=False,
        )

        return True

    def test_file_locking(self) -> bool:
        """Test cross-platform file locking with portalocker."""
        print("\n" + "=" * 60)
        print("File Locking Tests (portalocker)")
        print("=" * 60)

        test_code = """
import tempfile
import json
from pathlib import Path
from arcade_core.usage.identity import UsageIdentity
import os

# Create a temporary directory for testing
with tempfile.TemporaryDirectory() as tmpdir:
    # Monkey patch the config path
    import arcade_core.usage.identity as identity_module
    original_path = identity_module.ARCADE_CONFIG_PATH
    identity_module.ARCADE_CONFIG_PATH = tmpdir

    try:
        # Create identity instance and test file operations
        identity = UsageIdentity()

        # Test load_or_create (uses file locking)
        data1 = identity.load_or_create()
        assert "anon_id" in data1
        print(f"✅ Successfully created identity with anon_id: {data1['anon_id']}")

        # Test that we can read it back (uses shared lock)
        identity2 = UsageIdentity()
        data2 = identity2.load_or_create()
        assert data2["anon_id"] == data1["anon_id"]
        print(f"✅ Successfully read identity with file locking")

        # Test atomic write (uses exclusive lock)
        identity.set_linked_principal_id("test-principal-123")
        data3 = identity.load_or_create()
        assert data3["linked_principal_id"] == "test-principal-123"
        print(f"✅ Successfully wrote identity with file locking")

        print("✅ All portalocker file locking tests passed!")
    finally:
        identity_module.ARCADE_CONFIG_PATH = original_path
"""

        # Use uv run to ensure we're in the right environment
        python_cmd = ["uv", "run", "python", "-c", test_code]
        success, _ = self.run_command(
            python_cmd,
            "Test portalocker file locking (cross-platform)",
            required=True,
        )
        return success

    def run_all_tests(self) -> int:
        """Run all tests and return exit code."""
        print("=" * 60)
        print("Testing arcade-mcp Installation from Source")
        print("=" * 60)
        print(f"\nProject root: {self.project_root}")

        # Run test phases
        test_phases = [
            ("Prerequisites", self.check_prerequisites),
            ("Installation", self.install_package),
            ("CLI Functionality", self.test_cli_availability),
            ("File Locking", self.test_file_locking),
        ]

        for phase_name, test_func in test_phases:
            if not test_func():
                print(f"\n❌ {phase_name} phase failed. Stopping tests.")
                return 1

        # Print summary
        self.print_summary()
        return 0

    def print_summary(self) -> None:
        """Print test summary."""
        print("\n" + "=" * 60)
        print("Test Summary")
        print("=" * 60)

        passed = sum(1 for _, success in self.test_results if success)
        total = len(self.test_results)

        for description, success in self.test_results:
            status = "✅ PASSED" if success else "❌ FAILED"
            print(f"{status}: {description}")

        print(f"\nTotal: {passed}/{total} tests passed")

        if passed == total:
            print("\n🎉 All tests passed! arcade-mcp is working correctly.")
        else:
            print(f"\n⚠️  {total - passed} test(s) failed.")


def main() -> int:
    """Main entry point."""
    # Get project root (two levels up from tests/install/)
    project_root = Path(__file__).parent.parent.parent.absolute()
    runner = TestRunner(project_root)
    return runner.run_all_tests()


def test_installation() -> None:
    """Pytest entry point for the installation test suite.

    Delegates to the existing TestRunner so the full install validation
    runs under pytest (picks up conftest.py fixtures such as
    disable_usage_tracking) without changing the internal test logic.
    """
    exit_code = main()
    assert exit_code == 0, "One or more installation tests failed — see output above."


def test_sync_dependencies_command_default(tmp_path: Path) -> None:
    """Use the baseline sync command outside Windows+pytest context."""
    runner = TestRunner(tmp_path)

    assert runner._sync_dependencies_command(current_test="") == ["uv", "sync", "--dev"]


def test_sync_dependencies_command_pytest_context(tmp_path: Path) -> None:
    """Avoid reinstalling pytest when this suite itself is running."""
    runner = TestRunner(tmp_path)

    assert runner._sync_dependencies_command(
        current_test="tests/install/test_install.py::test_installation (call)",
    ) == ["uv", "sync", "--dev", "--inexact", "--no-install-package", "pytest"]


if __name__ == "__main__":
    sys.exit(main())
