"""
Tests for Phase 4.2: Custom Python Path Support.

Tests ephemeral kernelspec registration for custom Python environments.
"""

import json
import pytest
from pathlib import Path
from unittest.mock import patch, AsyncMock, MagicMock

from skop_runner.jupyter_backend.server_manager import JupyterServerManager


class TestRegisterEphemeralKernelspec:
    """Tests for JupyterServerManager.register_ephemeral_kernelspec()"""

    @pytest.fixture
    def manager(self):
        """Create a JupyterServerManager instance."""
        return JupyterServerManager()

    @pytest.fixture
    def mock_skop_dir(self, tmp_path, monkeypatch):
        """Mock ~/.skop to use a temp directory."""
        skop_dir = tmp_path / ".skop"
        skop_dir.mkdir()

        # Patch Path.home() to return our temp directory
        monkeypatch.setattr(Path, "home", lambda: tmp_path)
        return skop_dir

    @pytest.mark.asyncio
    async def test_creates_kernelspec_directory(self, manager, mock_skop_dir):
        """Kernelspec directory is created with correct structure."""
        python_path = "/usr/bin/python3"

        spec_name = await manager.register_ephemeral_kernelspec(python_path)

        # Verify directory was created
        kernelspec_dir = mock_skop_dir / "kernels" / spec_name
        assert kernelspec_dir.exists()
        assert kernelspec_dir.is_dir()

        # Verify kernel.json was created
        kernel_json_path = kernelspec_dir / "kernel.json"
        assert kernel_json_path.exists()

    @pytest.mark.asyncio
    async def test_kernel_json_has_correct_structure(self, manager, mock_skop_dir):
        """kernel.json contains correct argv, display_name, and language."""
        python_path = "/home/user/.venv/bin/python"

        spec_name = await manager.register_ephemeral_kernelspec(python_path)

        kernel_json_path = mock_skop_dir / "kernels" / spec_name / "kernel.json"
        kernel_json = json.loads(kernel_json_path.read_text())

        # Check required fields
        assert kernel_json["argv"] == [
            python_path,
            "-m",
            "ipykernel_launcher",
            "-f",
            "{connection_file}",
        ]
        assert kernel_json["language"] == "python"
        assert "display_name" in kernel_json
        assert kernel_json["metadata"]["skop_managed"] is True
        assert kernel_json["metadata"]["python_path"] == python_path

    @pytest.mark.asyncio
    async def test_spec_name_is_deterministic(self, manager, mock_skop_dir):
        """Same python_path always produces same spec_name."""
        python_path = "/usr/bin/python3"

        spec_name1 = await manager.register_ephemeral_kernelspec(python_path)
        spec_name2 = await manager.register_ephemeral_kernelspec(python_path)

        assert spec_name1 == spec_name2
        assert spec_name1.startswith("skop-")

    @pytest.mark.asyncio
    async def test_different_paths_produce_different_names(self, manager, mock_skop_dir):
        """Different python_paths produce different spec_names."""
        spec_name1 = await manager.register_ephemeral_kernelspec("/usr/bin/python3")
        spec_name2 = await manager.register_ephemeral_kernelspec("/home/user/.venv/bin/python")

        assert spec_name1 != spec_name2

    @pytest.mark.asyncio
    async def test_env_name_used_in_display_name(self, manager, mock_skop_dir):
        """env_name parameter is used in the display_name."""
        python_path = "/home/user/.venv/bin/python"
        env_name = "my-project-env"

        spec_name = await manager.register_ephemeral_kernelspec(python_path, env_name)

        kernel_json_path = mock_skop_dir / "kernels" / spec_name / "kernel.json"
        kernel_json = json.loads(kernel_json_path.read_text())

        assert env_name in kernel_json["display_name"]
        assert kernel_json["display_name"] == f"Python ({env_name})"

    @pytest.mark.asyncio
    async def test_default_display_name_uses_parent_directory(self, manager, mock_skop_dir):
        """Without env_name, display_name uses parent directory name."""
        python_path = "/home/user/my-project/.venv/bin/python"

        spec_name = await manager.register_ephemeral_kernelspec(python_path)

        kernel_json_path = mock_skop_dir / "kernels" / spec_name / "kernel.json"
        kernel_json = json.loads(kernel_json_path.read_text())

        # Parent of parent of 'python' is '.venv'
        assert kernel_json["display_name"] == "Python (.venv)"

    @pytest.mark.asyncio
    async def test_overwrites_existing_spec(self, manager, mock_skop_dir):
        """Registering same path twice overwrites the kernelspec."""
        python_path = "/usr/bin/python3"

        # First registration with one env_name
        spec_name = await manager.register_ephemeral_kernelspec(python_path, "first")

        kernel_json_path = mock_skop_dir / "kernels" / spec_name / "kernel.json"
        kernel_json1 = json.loads(kernel_json_path.read_text())
        assert "first" in kernel_json1["display_name"]

        # Second registration with different env_name
        await manager.register_ephemeral_kernelspec(python_path, "second")

        kernel_json2 = json.loads(kernel_json_path.read_text())
        assert "second" in kernel_json2["display_name"]


class TestJupyterPathConfiguration:
    """Tests for JUPYTER_PATH environment variable setup."""

    @pytest.mark.asyncio
    async def test_jupyter_path_includes_skop_dir(self, tmp_path, monkeypatch):
        """Start server sets JUPYTER_PATH to include ~/.skop."""
        # This test verifies the environment setup, not actual server start
        from skop_runner.jupyter_backend.server_manager import JupyterServerManager
        import os

        manager = JupyterServerManager()

        # Mock the subprocess creation to capture the environment
        captured_env = {}

        async def mock_create_subprocess(*args, **kwargs):
            captured_env.update(kwargs.get("env", {}))
            # Simulate process that exits immediately
            mock_process = AsyncMock()
            mock_process.returncode = 1  # Simulate failure
            mock_process.pid = 12345
            mock_process.stdout = AsyncMock()
            mock_process.stdout.readline = AsyncMock(return_value=b"")
            mock_process.stderr = AsyncMock()
            mock_process.stderr.readline = AsyncMock(return_value=b"")
            mock_process.wait = AsyncMock(return_value=1)
            mock_process.terminate = MagicMock()
            mock_process.kill = MagicMock()
            return mock_process

        monkeypatch.setattr(Path, "home", lambda: tmp_path)

        with patch("asyncio.create_subprocess_exec", mock_create_subprocess):
            try:
                await manager.start_server(root_dir=str(tmp_path))
            except RuntimeError:
                pass  # Expected - server won't actually start

        # Verify JUPYTER_PATH was set correctly
        assert "JUPYTER_PATH" in captured_env
        skop_dir = str(tmp_path / ".skop")
        assert skop_dir in captured_env["JUPYTER_PATH"]


