"""Tests for carta.embed.colpali module."""

import sys
from pathlib import Path
from unittest.mock import MagicMock, patch, Mock

import pytest


# Minimal config fixture
def _minimal_cfg():
    return {
        "project_name": "test",
        "qdrant_url": "http://localhost:6333",
        "embed": {
            "ollama_url": "http://localhost:11434",
            "ollama_model": "nomic-embed-text:latest",
            "colpali_enabled": True,
            "colpali_model": "vidore/colqwen2-v1.0",
            "colpali_device": "cpu",
            "colpali_batch_size": 1,
            "colpali_sidecar_path": ".carta/visual_cache/",
        },
    }


class TestIsColpaliAvailable:
    """Tests for is_colpali_available() function."""

    def test_returns_false_when_transformers_not_installed(self):
        """Should return False when transformers ColPali support is not available."""
        # Patch the module-level flag set at import time
        with patch("carta.embed.colpali._COLPALI_AVAILABLE", False):
            from carta.embed.colpali import is_colpali_available

            result = is_colpali_available()
            assert result is False


class TestColPaliEmbedderInit:
    """Tests for ColPaliEmbedder initialization."""

    def test_raises_import_error_when_colpali_unavailable(self):
        """Should raise ImportError when colpali-engine is not installed."""
        with patch("carta.embed.colpali._COLPALI_AVAILABLE", False):
            from carta.embed.colpali import ColPaliEmbedder

            with pytest.raises(ImportError) as exc_info:
                ColPaliEmbedder()

            assert "transformers" in str(exc_info.value)

    @patch("carta.embed.colpali._COLPALI_AVAILABLE", True)
    @patch("carta.embed.colpali.np", MagicMock())
    @patch("carta.embed.colpali.Image", MagicMock())
    def test_init_with_default_params(self):
        """Should initialize with default parameters."""
        from carta.embed.colpali import ColPaliEmbedder

        with patch.object(Path, "mkdir"):
            embedder = ColPaliEmbedder()

        assert embedder.model_name == "vidore/colqwen2-v1.0-hf"
        assert embedder.device == "cpu"
        assert embedder.batch_size == 1
        assert embedder.cache_dir == Path(".carta/visual_cache/")

    @patch("carta.embed.colpali._COLPALI_AVAILABLE", True)
    @patch("carta.embed.colpali.np", MagicMock())
    @patch("carta.embed.colpali.Image", MagicMock())
    def test_init_with_custom_params(self):
        """Should initialize with custom parameters."""
        from carta.embed.colpali import ColPaliEmbedder

        with patch.object(Path, "mkdir"):
            embedder = ColPaliEmbedder(
                model_name="vidore/colpali-v1.3",
                device="cuda",
                batch_size=4,
                cache_dir="/custom/cache",
            )

        assert embedder.model_name == "vidore/colpali-v1.3"
        assert embedder.batch_size == 4


class TestColPaliEmbedderDeviceResolution:
    """Tests for device resolution logic."""

    @patch("carta.embed.colpali._COLPALI_AVAILABLE", True)
    @patch("carta.embed.colpali.np", MagicMock())
    @patch("carta.embed.colpali.Image", MagicMock())
    @patch("carta.embed.colpali.torch")
    def test_cuda_fallback_to_cpu_when_unavailable(self, mock_torch):
        """Should fall back to CPU when CUDA is requested but unavailable."""
        from carta.embed.colpali import ColPaliEmbedder

        mock_torch.cuda.is_available.return_value = False

        with patch.object(Path, "mkdir"):
            embedder = ColPaliEmbedder(device="cuda")

        assert embedder.device == "cpu"


class TestColPaliEmbedderSavePageCache:
    """Tests for save_page_cache method."""

    @patch("carta.embed.colpali._COLPALI_AVAILABLE", True)
    @patch("carta.embed.colpali.np", MagicMock())
    @patch("carta.embed.colpali.Image", MagicMock())
    def test_save_page_cache_creates_directory_and_file(self):
        """Should create cache directory and save PNG file."""
        from carta.embed.colpali import ColPaliEmbedder

        with patch.object(Path, "mkdir") as mock_mkdir, \
             patch.object(Path, "write_bytes") as mock_write:
            embedder = ColPaliEmbedder(cache_dir="/test/cache")
            pdf_path = Path("/docs/datasheet.pdf")
            png_bytes = b"fake_png_data"

            result = embedder.save_page_cache(pdf_path, 42, png_bytes)

            # Should create the PDF-specific cache directory
            mock_mkdir.assert_called_with(parents=True, exist_ok=True)
            # Should write the PNG file
            mock_write.assert_called_with(png_bytes)
            # Result should be the path to the saved file
            assert "datasheet" in str(result)
            assert "page_0042.png" in str(result)


class TestColPaliError:
    """Tests for ColPaliError exception."""

    def test_colpali_error_is_exception(self):
        """ColPaliError should inherit from Exception."""
        from carta.embed.colpali import ColPaliError

        with pytest.raises(ColPaliError):
            raise ColPaliError("test error")


class TestModuleConstants:
    """Tests for module-level constants."""

    def test_vector_dim_is_128(self):
        """VECTOR_DIM should be 128 for ColPali."""
        from carta.embed.colpali import VECTOR_DIM

        assert VECTOR_DIM == 128

    def test_default_render_dpi_is_150(self):
        """DEFAULT_RENDER_DPI should be 150."""
        from carta.embed.colpali import DEFAULT_RENDER_DPI

        assert DEFAULT_RENDER_DPI == 150


class TestEmbedQuery:
    """Tests for embed_query method (Issue #1 - visual search)."""

    @patch("carta.embed.colpali._COLPALI_AVAILABLE", True)
    @patch("carta.embed.colpali.np", MagicMock())
    @patch("carta.embed.colpali.Image", MagicMock())
    def test_embed_query_requires_loaded_model(self):
        """embed_query should load model if not already loaded."""
        from carta.embed.colpali import ColPaliEmbedder

        with patch.object(Path, "mkdir"):
            embedder = ColPaliEmbedder()
            
        # Mock _load_model
        embedder._load_model = MagicMock()
        embedder._model = MagicMock()
        embedder._processor = MagicMock()
        
        # Mock the processor and model behavior
        # Native transformers API: processor(text=[...], return_tensors="pt")
        # returns a BatchEncoding object with .to() method
        mock_processed = MagicMock()
        mock_processed.input_ids = MagicMock()
        mock_processed.attention_mask = MagicMock()
        mock_processed.to = MagicMock(return_value=mock_processed)
        embedder._processor = MagicMock(return_value=mock_processed)
        
        # Mock torch and outputs
        mock_outputs = MagicMock()
        mock_outputs.__getitem__ = MagicMock(return_value=MagicMock())
        embedder._model.return_value = mock_outputs
        
        with patch("carta.embed.colpali.torch"):
            # Should not raise even though model wasn't loaded yet
            try:
                embedder.embed_query("test query")
                # If model was None, _load_model would be called
            except Exception:
                pass  # Expected since we're heavily mocking

    @patch("carta.embed.colpali._COLPALI_AVAILABLE", True)
    @patch("carta.embed.colpali.np", MagicMock())
    @patch("carta.embed.colpali.Image", MagicMock())
    def test_embed_query_raises_colpali_error_on_failure(self):
        """embed_query should raise ColPaliError on processing failure."""
        from carta.embed.colpali import ColPaliEmbedder, ColPaliError

        with patch.object(Path, "mkdir"):
            embedder = ColPaliEmbedder()
            
        embedder._model = MagicMock()
        embedder._processor = MagicMock()
        # Native transformers API: processor(text=[...]) call raises
        embedder._processor = MagicMock(side_effect=RuntimeError("processing failed"))
        
        with pytest.raises(ColPaliError):
            embedder.embed_query("test query")
