"""tests/test_features_visual_caption_bridge.py — Unit tests for VisualCaptionBridge."""
import pytest
import numpy as np
from unittest.mock import MagicMock, patch

pytestmark = pytest.mark.unit


class TestImport:
    def test_import(self):
        from llm_guard.features.visual_caption_bridge import VisualCaptionBridge
        assert VisualCaptionBridge is not None


class TestInstantiation:
    def test_default_construction(self):
        from llm_guard.features.visual_caption_bridge import VisualCaptionBridge
        b = VisualCaptionBridge()
        assert b.embedding_model == "all-MiniLM-L6-v2"
        assert b.device == "cpu"

    def test_custom_params(self):
        from llm_guard.features.visual_caption_bridge import VisualCaptionBridge
        b = VisualCaptionBridge(embedding_model="custom-model", device="cuda")
        assert b.embedding_model == "custom-model"
        assert b.device == "cuda"

    def test_lazy_model_not_loaded(self):
        from llm_guard.features.visual_caption_bridge import VisualCaptionBridge
        b = VisualCaptionBridge()
        assert b._sent_model is None
        assert b._blip2_model is None


class TestScoreGrounding:
    def _make_bridge_with_mock_embedder(self):
        from llm_guard.features.visual_caption_bridge import VisualCaptionBridge
        b = VisualCaptionBridge()
        mock_model = MagicMock()
        # Return unit-norm vectors
        v1 = np.array([1.0, 0.0, 0.0])
        v2 = np.array([1.0, 0.0, 0.0])
        mock_model.encode.side_effect = [
            v1.reshape(1, -1), v2.reshape(1, -1)
        ]
        b._sent_model = mock_model
        return b

    def test_raises_without_image_or_description(self):
        from llm_guard.features.visual_caption_bridge import VisualCaptionBridge
        b = VisualCaptionBridge()
        with pytest.raises(ValueError):
            b.score_grounding("some text")

    def test_score_with_description_returns_float(self):
        from llm_guard.features.visual_caption_bridge import VisualCaptionBridge
        b = VisualCaptionBridge()
        # Mock the embedding method
        with patch.object(b, "_embed_text", side_effect=[
            np.array([1.0, 0.0, 0.0]),
            np.array([1.0, 0.0, 0.0]),
        ]):
            score = b.score_grounding("Agent says X.", image_description="Image shows X.")
        assert isinstance(score, float)
        assert 0.0 <= score <= 1.0

    def test_identical_text_scores_high(self):
        from llm_guard.features.visual_caption_bridge import VisualCaptionBridge
        b = VisualCaptionBridge()
        vec = np.array([0.6, 0.8, 0.0])
        with patch.object(b, "_embed_text", return_value=vec):
            score = b.score_grounding("Text", image_description="Same text")
        assert score > 0.9

    def test_orthogonal_vectors_score_zero(self):
        from llm_guard.features.visual_caption_bridge import VisualCaptionBridge
        b = VisualCaptionBridge()
        v1 = np.array([1.0, 0.0, 0.0])
        v2 = np.array([0.0, 1.0, 0.0])
        with patch.object(b, "_embed_text", side_effect=[v1, v2]):
            score = b.score_grounding("Text A", image_description="Text B")
        assert score == pytest.approx(0.0, abs=0.01)

    def test_score_risk_is_complement(self):
        from llm_guard.features.visual_caption_bridge import VisualCaptionBridge
        b = VisualCaptionBridge()
        vec = np.array([0.6, 0.8, 0.0])
        with patch.object(b, "_embed_text", return_value=vec):
            grounding = b.score_grounding("Text", image_description="Description")
        vec2 = np.array([0.6, 0.8, 0.0])
        with patch.object(b, "_embed_text", return_value=vec2):
            risk = b.score_risk("Text", image_description="Description")
        assert grounding + risk == pytest.approx(1.0, abs=1e-5)
