"""Integration tests for the complete runner workflow."""

import pytest
import tempfile
from pathlib import Path

from skop_runner.jupyter_backend import JupyterBackend
from skop_runner.file_manager import FileManager
from skop_runner.message_handler import MessageHandler


@pytest.fixture
def temp_project():
    """Create a temporary project directory."""
    with tempfile.TemporaryDirectory() as tmpdir:
        project = Path(tmpdir)
        yield project


@pytest.fixture
async def integration_handler(temp_project):
    """Create a full handler stack for integration testing."""
    backend = JupyterBackend()
    await backend.initialize(root_dir=str(temp_project))
    fm = FileManager(str(temp_project))
    handler = MessageHandler(backend, fm)
    yield handler, backend, fm
    await backend.close()


@pytest.mark.integration
class TestFullWorkflow:
    """Test complete workflow: file ops + code execution."""

    @pytest.mark.asyncio
    async def test_create_and_execute_file(self, temp_project):
        """Test creating a file and executing its contents."""
        backend = JupyterBackend()
        await backend.initialize(root_dir=str(temp_project))
        fm = FileManager(str(temp_project))
        handler = MessageHandler(backend, fm)

        try:
            # 1. Create a test file
            test_content = "x = 42\nprint(f'The answer is {x}')"

            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "1",
                "method": "file.write",
                "params": {"path": "test_script.py", "content": test_content}
            })
            assert result["result"]["success"] is True

            # 2. Verify file was created
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "2",
                "method": "file.exists",
                "params": {"path": "test_script.py"}
            })
            assert result["result"]["exists"] is True

            # 3. Read the file back
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "3",
                "method": "file.read",
                "params": {"path": "test_script.py"}
            })
            assert result["result"]["content"] == test_content

            # 4. Start a kernel
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "4",
                "method": "kernel.start",
                "params": {}
            })
            assert "kernel_id" in result["result"]
            kernel_id = result["result"]["kernel_id"]

            # 5. Execute the file contents
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "5",
                "method": "kernel.execute",
                "params": {"kernel_id": kernel_id, "code": test_content}
            })
            outputs = result["result"]["outputs"]

            # Should have stream output with our print statement
            stream_outputs = [o for o in outputs if o["output_type"] == "stream"]
            assert len(stream_outputs) > 0
            assert "The answer is 42" in stream_outputs[0]["text"]

            # 6. Stop the kernel
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "6",
                "method": "kernel.stop",
                "params": {"kernel_id": kernel_id}
            })
            assert result["result"]["success"] is True

        finally:
            await backend.close()

    @pytest.mark.asyncio
    async def test_file_operations_workflow(self, temp_project):
        """Test a complete file operations workflow."""
        backend = JupyterBackend()
        await backend.initialize(root_dir=str(temp_project))
        fm = FileManager(str(temp_project))
        handler = MessageHandler(backend, fm)

        try:
            # 1. List empty directory
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "1",
                "method": "file.list",
                "params": {"path": ""}
            })
            assert result["result"]["items"] == []

            # 2. Create a folder
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "2",
                "method": "file.createFolder",
                "params": {"path": "src"}
            })
            assert result["result"]["success"] is True

            # 3. Create files in the folder
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "3",
                "method": "file.write",
                "params": {"path": "src/main.py", "content": "print('hello')"}
            })
            assert result["result"]["success"] is True

            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "4",
                "method": "file.write",
                "params": {"path": "src/utils.py", "content": "def helper(): pass"}
            })
            assert result["result"]["success"] is True

            # 4. List directory
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "5",
                "method": "file.list",
                "params": {"path": "src"}
            })
            items = result["result"]["items"]
            names = [i["name"] for i in items]
            assert "main.py" in names
            assert "utils.py" in names

            # 5. Get file info
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "6",
                "method": "file.info",
                "params": {"path": "src/main.py"}
            })
            info = result["result"]["info"]
            assert info["name"] == "main.py"
            assert info["type"] == "file"

            # 6. Rename file
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "7",
                "method": "file.rename",
                "params": {"old_path": "src/utils.py", "new_path": "src/helpers.py"}
            })
            assert result["result"]["success"] is True

            # 7. Verify rename
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "8",
                "method": "file.exists",
                "params": {"path": "src/utils.py"}
            })
            assert result["result"]["exists"] is False

            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "9",
                "method": "file.exists",
                "params": {"path": "src/helpers.py"}
            })
            assert result["result"]["exists"] is True

            # 8. Delete file
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "10",
                "method": "file.delete",
                "params": {"path": "src/helpers.py"}
            })
            assert result["result"]["success"] is True

            # 9. Delete folder
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "11",
                "method": "file.delete",
                "params": {"path": "src"}
            })
            assert result["result"]["success"] is True

        finally:
            await backend.close()

    @pytest.mark.asyncio
    async def test_kernel_state_persistence(self, temp_project):
        """Test that kernel state persists across executions."""
        backend = JupyterBackend()
        await backend.initialize(root_dir=str(temp_project))
        fm = FileManager(str(temp_project))
        handler = MessageHandler(backend, fm)

        try:
            # Start kernel
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "1",
                "method": "kernel.start",
                "params": {}
            })
            kernel_id = result["result"]["kernel_id"]

            # Define a variable
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "2",
                "method": "kernel.execute",
                "params": {"kernel_id": kernel_id, "code": "my_var = 'persistent'"}
            })

            # Use the variable in a new execution
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "3",
                "method": "kernel.execute",
                "params": {"kernel_id": kernel_id, "code": "print(my_var)"}
            })
            outputs = result["result"]["outputs"]
            stream_outputs = [o for o in outputs if o["output_type"] == "stream"]
            assert any("persistent" in o["text"] for o in stream_outputs)

            # Define a function
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "4",
                "method": "kernel.execute",
                "params": {"kernel_id": kernel_id, "code": "def greet(name): return f'Hello, {name}!'"}
            })

            # Call the function
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "5",
                "method": "kernel.execute",
                "params": {"kernel_id": kernel_id, "code": "greet('World')"}
            })
            outputs = result["result"]["outputs"]
            exec_results = [o for o in outputs if o["output_type"] == "execute_result"]
            assert any("Hello, World!" in str(o) for o in exec_results)

        finally:
            await backend.close()

    @pytest.mark.asyncio
    async def test_multiple_kernels(self, temp_project):
        """Test running multiple kernels simultaneously."""
        backend = JupyterBackend()
        await backend.initialize(root_dir=str(temp_project))
        fm = FileManager(str(temp_project))
        handler = MessageHandler(backend, fm)

        try:
            # Start two kernels
            result1 = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "1",
                "method": "kernel.start",
                "params": {}
            })
            kernel_id_1 = result1["result"]["kernel_id"]

            result2 = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "2",
                "method": "kernel.start",
                "params": {}
            })
            kernel_id_2 = result2["result"]["kernel_id"]

            # Set different variables in each kernel
            await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "3",
                "method": "kernel.execute",
                "params": {"kernel_id": kernel_id_1, "code": "kernel_name = 'Kernel A'"}
            })

            await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "4",
                "method": "kernel.execute",
                "params": {"kernel_id": kernel_id_2, "code": "kernel_name = 'Kernel B'"}
            })

            # Verify each kernel has its own state
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "5",
                "method": "kernel.execute",
                "params": {"kernel_id": kernel_id_1, "code": "print(kernel_name)"}
            })
            outputs = result["result"]["outputs"]
            stream = [o for o in outputs if o["output_type"] == "stream"]
            assert any("Kernel A" in o["text"] for o in stream)

            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "6",
                "method": "kernel.execute",
                "params": {"kernel_id": kernel_id_2, "code": "print(kernel_name)"}
            })
            outputs = result["result"]["outputs"]
            stream = [o for o in outputs if o["output_type"] == "stream"]
            assert any("Kernel B" in o["text"] for o in stream)

            # List kernels
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "7",
                "method": "kernel.list",
                "params": {}
            })
            kernels = result["result"]["kernels"]
            assert len(kernels) == 2

        finally:
            await backend.close()

    @pytest.mark.asyncio
    async def test_workspace_root(self, temp_project):
        """Test workspace.getRoot returns correct path."""
        backend = JupyterBackend()
        await backend.initialize(root_dir=str(temp_project))
        fm = FileManager(str(temp_project))
        handler = MessageHandler(backend, fm)

        try:
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "1",
                "method": "workspace.getRoot",
                "params": {}
            })

            # Handle macOS /var -> /private/var symlink
            assert result["result"]["root"] == str(temp_project.resolve())

        finally:
            await backend.close()

    @pytest.mark.asyncio
    async def test_error_handling(self, temp_project):
        """Test that errors are properly returned in JSON-RPC format."""
        backend = JupyterBackend()
        await backend.initialize(root_dir=str(temp_project))
        fm = FileManager(str(temp_project))
        handler = MessageHandler(backend, fm)

        try:
            # Test unknown method
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "1",
                "method": "unknown.method",
                "params": {}
            })
            assert "error" in result
            assert result["error"]["code"] == -32601

            # Test missing file
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "2",
                "method": "file.read",
                "params": {"path": "nonexistent.txt"}
            })
            assert "error" in result
            assert result["error"]["code"] == -32001

            # Test kernel not found
            result = await handler.handle_message({
                "jsonrpc": "2.0",
                "id": "3",
                "method": "kernel.execute",
                "params": {"kernel_id": "fake-kernel", "code": "print('test')"}
            })
            assert "error" in result

        finally:
            await backend.close()
