"""Tests for file.readBinary JSON-RPC method."""

import base64
import pytest
import tempfile
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock

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


@pytest.fixture
def temp_project():
    """Create a temporary project directory with test files."""
    with tempfile.TemporaryDirectory() as tmpdir:
        project = Path(tmpdir)
        # Create a small PNG file (1x1 red pixel)
        png_data = (
            b"\x89PNG\r\n\x1a\n"  # PNG signature
            b"\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01"
            b"\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDATx"
            b"\x9cc\xf8\x0f\x00\x00\x01\x01\x00\x05\x18\xd8N\x00"
            b"\x00\x00\x00IEND\xaeB`\x82"
        )
        (project / "test.png").write_bytes(png_data)
        (project / "test.txt").write_text("hello world")
        (project / "test.pdf").write_bytes(b"%PDF-1.4 fake pdf content")
        (project / "test.mp3").write_bytes(b"\xff\xfb\x90\x00" + b"\x00" * 100)
        (project / "test.mp4").write_bytes(b"\x00\x00\x00\x1cftyp" + b"\x00" * 100)
        (project / "test.svg").write_bytes(
            b'<svg xmlns="http://www.w3.org/2000/svg"><rect/></svg>'
        )
        (project / "subdir").mkdir()
        yield project


@pytest.fixture
def handler(temp_project):
    """Create a message handler."""
    backend = MagicMock()
    backend.initialize = AsyncMock()
    backend.close = AsyncMock()
    fm = FileManager(str(temp_project))
    return MessageHandler(backend, fm)


def _make_request(method, params, msg_id="1"):
    return {"jsonrpc": "2.0", "id": msg_id, "method": method, "params": params}


class TestFileReadBinary:
    """Tests for file.readBinary JSON-RPC method."""

    @pytest.mark.asyncio
    async def test_read_png_returns_valid_base64(self, handler):
        """Test file.readBinary returns valid base64 for a PNG file."""
        response = await handler.handle_message(
            _make_request("file.readBinary", {"path": "test.png"})
        )
        assert "result" in response
        result = response["result"]
        assert "content" in result
        assert "mimeType" in result
        assert "size" in result
        # Content should be valid base64
        decoded = base64.b64decode(result["content"])
        assert len(decoded) > 0
        # Should start with PNG signature
        assert decoded[:4] == b"\x89PNG"

    @pytest.mark.asyncio
    async def test_mime_type_detection(self, handler):
        """Test correct MIME type detection for various file types."""
        test_cases = [
            ("test.png", "image/png"),
            ("test.pdf", "application/pdf"),
            ("test.mp3", "audio/mpeg"),
            ("test.mp4", "video/mp4"),
            ("test.svg", "image/svg+xml"),
        ]
        for filename, expected_mime in test_cases:
            response = await handler.handle_message(
                _make_request("file.readBinary", {"path": filename})
            )
            assert "result" in response, f"Error for {filename}: {response}"
            assert response["result"]["mimeType"] == expected_mime, (
                f"Expected {expected_mime} for {filename}, "
                f"got {response['result']['mimeType']}"
            )

    @pytest.mark.asyncio
    async def test_returns_correct_file_size(self, handler, temp_project):
        """Test file.readBinary returns correct original file size."""
        response = await handler.handle_message(
            _make_request("file.readBinary", {"path": "test.png"})
        )
        expected_size = (temp_project / "test.png").stat().st_size
        assert response["result"]["size"] == expected_size

    @pytest.mark.asyncio
    async def test_rejects_files_exceeding_max_size(self, handler, temp_project):
        """Test file.readBinary rejects files exceeding maxSize parameter."""
        # Create a file larger than our maxSize limit
        (temp_project / "large.bin").write_bytes(b"\x00" * 1000)

        response = await handler.handle_message(
            _make_request(
                "file.readBinary", {"path": "large.bin", "maxSize": 500}
            )
        )
        assert "error" in response
        assert "too large" in response["error"]["message"].lower()

    @pytest.mark.asyncio
    async def test_nonexistent_file_returns_error(self, handler):
        """Test file.readBinary with nonexistent file returns error."""
        response = await handler.handle_message(
            _make_request("file.readBinary", {"path": "nonexistent.png"})
        )
        assert "error" in response

    @pytest.mark.asyncio
    async def test_path_traversal_rejected(self, handler):
        """Test file.readBinary rejects path traversal attempts."""
        response = await handler.handle_message(
            _make_request("file.readBinary", {"path": "../../etc/passwd"})
        )
        assert "error" in response

    @pytest.mark.asyncio
    async def test_text_file_works(self, handler):
        """Test file.readBinary works on text files too (returns base64)."""
        response = await handler.handle_message(
            _make_request("file.readBinary", {"path": "test.txt"})
        )
        assert "result" in response
        decoded = base64.b64decode(response["result"]["content"])
        assert decoded == b"hello world"
        assert response["result"]["mimeType"] == "text/plain"

    @pytest.mark.asyncio
    async def test_unknown_mime_type_fallback(self, handler, temp_project):
        """Test file.readBinary returns application/octet-stream for unknown types."""
        (temp_project / "data.xyz123").write_bytes(b"\x00\x01\x02")
        response = await handler.handle_message(
            _make_request("file.readBinary", {"path": "data.xyz123"})
        )
        assert "result" in response
        assert response["result"]["mimeType"] == "application/octet-stream"

    @pytest.mark.asyncio
    async def test_default_max_size_is_50mb(self, handler, temp_project):
        """Test that maxSize defaults to 50MB when not specified."""
        # A small file should work fine without maxSize
        response = await handler.handle_message(
            _make_request("file.readBinary", {"path": "test.png"})
        )
        assert "result" in response

    @pytest.mark.asyncio
    async def test_max_size_capped_at_50mb(self, handler, temp_project):
        """Test that maxSize cannot exceed MAX_FILE_CONTENT_SIZE (50MB)."""
        # Even if caller requests 100MB, it should be capped
        # This just verifies the call succeeds (the cap is internal)
        response = await handler.handle_message(
            _make_request(
                "file.readBinary",
                {"path": "test.png", "maxSize": 100 * 1024 * 1024},
            )
        )
        assert "result" in response
