"""
Tests for Google embeddings implementation (Gemini API + Vertex AI).

These tests cover:
1. Initialization (Gemini API key, Vertex AI with ADC/service account)
2. Dimension detection via test embedding
3. Output dimensionality configuration
4. Encode (single text, multiple texts, batching, empty list, uninitialized)
5. Provider name and model name normalization
6. Factory function (create from env, validation errors)
"""

from typing import Any
from unittest.mock import MagicMock, patch

import pytest

from hindsight_api.config import (
    ENV_EMBEDDINGS_GEMINI_API_KEY,
    ENV_EMBEDDINGS_PROVIDER,
    HindsightConfig,
)
from hindsight_api.engine.embeddings import GeminiEmbeddings, create_embeddings_from_env


def _make_mock_embedding(values: list[float]) -> MagicMock:
    emb = MagicMock()
    emb.values = values
    return emb


def _make_mock_embed_result(embeddings_data: list[list[float]]) -> MagicMock:
    result = MagicMock()
    result.embeddings = [_make_mock_embedding(v) for v in embeddings_data]
    return result


def _make_mock_genai(embed_result: Any = None) -> MagicMock:
    if embed_result is None:
        embed_result = _make_mock_embed_result([[0.1] * 768])
    mock_genai = MagicMock()
    mock_client = MagicMock()
    mock_client.models.embed_content = MagicMock(return_value=embed_result)
    mock_genai.Client = MagicMock(return_value=mock_client)
    return mock_genai


def _make_mock_google_module(mock_genai: MagicMock) -> MagicMock:
    mod = MagicMock()
    mod.genai = mock_genai
    mod.genai.types.EmbedContentConfig = MagicMock(side_effect=lambda **kw: MagicMock(**kw))
    return mod


def _patch_google_import(mock_genai: MagicMock):
    original_import = __import__

    def mock_import(name, *args, **kwargs):
        if name == "google":
            return _make_mock_google_module(mock_genai)
        if name == "google.genai":
            return mock_genai
        return original_import(name, *args, **kwargs)

    return patch("builtins.__import__", side_effect=mock_import)


class TestGeminiEmbeddings:
    """Unit tests for GeminiEmbeddings with mocked google.genai."""

    async def test_initialization_api_key_success(self):
        """Test successful Gemini API key initialization."""
        mock_genai = _make_mock_genai()
        emb = GeminiEmbeddings(model="gemini-embedding-001", api_key="test-key")

        with _patch_google_import(mock_genai):
            await emb.initialize()

        assert emb._client is not None
        assert emb.dimension == 768
        assert emb.provider_name == "google"
        assert emb._is_vertexai is False
        mock_genai.Client.return_value.models.embed_content.assert_called_once()

    async def test_initialization_vertexai_success(self):
        """Test successful Vertex AI initialization."""
        mock_genai = _make_mock_genai()
        emb = GeminiEmbeddings(
            model="gemini-embedding-001",
            vertexai_project_id="test-project",
            vertexai_region="us-central1",
        )

        with _patch_google_import(mock_genai):
            await emb.initialize()

        assert emb._client is not None
        assert emb.dimension == 768
        assert emb.provider_name == "google"
        assert emb._is_vertexai is True
        mock_genai.Client.assert_called_once_with(
            vertexai=True,
            project="test-project",
            location="us-central1",
        )

    async def test_initialization_missing_api_key(self):
        """Test that missing API key raises ValueError when no vertexai_project_id."""
        mock_genai = _make_mock_genai()
        emb = GeminiEmbeddings(model="gemini-embedding-001", api_key=None)

        with _patch_google_import(mock_genai):
            with pytest.raises(ValueError, match="requires an API key"):
                await emb.initialize()

    async def test_initialization_vertexai_missing_project_id(self):
        """Test that Vertex AI mode requires project_id."""
        mock_genai = _make_mock_genai()
        emb = GeminiEmbeddings(model="gemini-embedding-001", vertexai_project_id="temp")
        emb.vertexai_project_id = None  # Simulate misconfiguration

        with _patch_google_import(mock_genai):
            with pytest.raises(ValueError, match="is required for Vertex AI"):
                await emb.initialize()

    async def test_initialization_idempotent(self):
        """Test that calling initialize() twice is a no-op."""
        mock_genai = _make_mock_genai()
        emb = GeminiEmbeddings(model="gemini-embedding-001", api_key="test-key")

        with _patch_google_import(mock_genai):
            await emb.initialize()
            first_client = emb._client
            await emb.initialize()
            assert emb._client is first_client

    async def test_dimension_detection_via_test_embedding(self):
        """Test that dimension is detected via a test embedding call."""
        test_embed = _make_mock_embed_result([[0.5] * 256])
        mock_genai = _make_mock_genai(embed_result=test_embed)
        emb = GeminiEmbeddings(model="some-new-model", api_key="test-key")

        with _patch_google_import(mock_genai):
            await emb.initialize()

        assert emb.dimension == 256

    async def test_output_dimensionality(self):
        """Test that output_dimensionality is passed via EmbedContentConfig."""
        test_embed = _make_mock_embed_result([[0.1] * 256])
        mock_genai = _make_mock_genai(embed_result=test_embed)
        emb = GeminiEmbeddings(model="gemini-embedding-001", api_key="test-key", output_dimensionality=256)

        with _patch_google_import(mock_genai):
            await emb.initialize()

        assert emb.dimension == 256
        assert emb._embed_config is not None
        call_kwargs = mock_genai.Client.return_value.models.embed_content.call_args
        assert "config" in call_kwargs.kwargs

    async def test_no_output_dimensionality(self):
        """Test that no EmbedContentConfig is built when output_dimensionality is None."""
        mock_genai = _make_mock_genai()
        emb = GeminiEmbeddings(model="gemini-embedding-001", api_key="test-key", output_dimensionality=None)

        with _patch_google_import(mock_genai):
            await emb.initialize()

        assert emb._embed_config is None
        call_kwargs = mock_genai.Client.return_value.models.embed_content.call_args
        assert "config" not in call_kwargs.kwargs

    def test_auto_detect_vertexai(self):
        """Test that _is_vertexai is auto-detected from vertexai_project_id."""
        assert GeminiEmbeddings(model="m", api_key="k")._is_vertexai is False
        assert GeminiEmbeddings(model="m", vertexai_project_id="p")._is_vertexai is True

    def test_encode_single_text(self):
        emb = GeminiEmbeddings(model="gemini-embedding-001", api_key="test-key")
        mock_client = MagicMock()
        mock_client.models.embed_content = MagicMock(return_value=_make_mock_embed_result([[0.1, 0.2, 0.3]]))
        emb._client = mock_client
        emb._dimension = 3

        assert emb.encode(["hello"]) == [[0.1, 0.2, 0.3]]

    def test_encode_multiple_texts(self):
        emb = GeminiEmbeddings(model="gemini-embedding-001", api_key="test-key")
        mock_client = MagicMock()
        mock_client.models.embed_content = MagicMock(
            return_value=_make_mock_embed_result([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]])
        )
        emb._client = mock_client
        emb._dimension = 2

        result = emb.encode(["a", "b", "c"])
        assert len(result) == 3
        assert result[1] == [0.3, 0.4]

    def test_encode_batching(self):
        emb = GeminiEmbeddings(model="gemini-embedding-001", api_key="test-key", batch_size=2)
        mock_client = MagicMock()
        mock_client.models.embed_content = MagicMock(
            side_effect=[_make_mock_embed_result([[0.1], [0.2]]), _make_mock_embed_result([[0.3]])]
        )
        emb._client = mock_client
        emb._dimension = 1

        assert emb.encode(["a", "b", "c"]) == [[0.1], [0.2], [0.3]]
        assert mock_client.models.embed_content.call_count == 2

    def test_encode_passes_config(self):
        emb = GeminiEmbeddings(model="gemini-embedding-001", api_key="test-key")
        mock_client = MagicMock()
        mock_client.models.embed_content = MagicMock(return_value=_make_mock_embed_result([[0.1, 0.2]]))
        emb._client = mock_client
        emb._dimension = 2
        emb._embed_config = MagicMock()

        emb.encode(["hello"])
        assert mock_client.models.embed_content.call_args.kwargs["config"] is emb._embed_config

    def test_encode_empty_list(self):
        emb = GeminiEmbeddings(model="gemini-embedding-001", api_key="test-key")
        emb._client = MagicMock()
        emb._dimension = 768
        assert emb.encode([]) == []

    def test_encode_before_initialization(self):
        emb = GeminiEmbeddings(model="gemini-embedding-001", api_key="test-key")
        with pytest.raises(RuntimeError, match="not initialized"):
            emb.encode(["test"])

    def test_dimension_before_initialization(self):
        emb = GeminiEmbeddings(model="gemini-embedding-001", api_key="test-key")
        with pytest.raises(RuntimeError, match="not initialized"):
            _ = emb.dimension

    def test_provider_name_always_google(self):
        assert GeminiEmbeddings(model="m", api_key="k").provider_name == "google"
        assert GeminiEmbeddings(model="m", vertexai_project_id="p").provider_name == "google"

    def test_vertexai_strips_google_prefix(self):
        mock_genai = _make_mock_genai()
        emb = GeminiEmbeddings(model="google/gemini-embedding-001", vertexai_project_id="test-project")
        emb._init_vertexai(mock_genai)
        assert emb.model == "gemini-embedding-001"

    def test_default_region(self):
        emb = GeminiEmbeddings(model="m", vertexai_project_id="proj")
        assert emb.vertexai_region == "us-central1"

    def test_custom_region(self):
        emb = GeminiEmbeddings(model="m", vertexai_project_id="proj", vertexai_region="europe-west1")
        assert emb.vertexai_region == "europe-west1"


class TestGeminiEmbeddingsFactory:
    """Tests for create_embeddings_from_env() with 'google' provider."""

    def _make_config(self, **overrides) -> HindsightConfig:
        from dataclasses import fields

        defaults = {}
        for f in fields(HindsightConfig):
            if f.type == "str":
                defaults[f.name] = ""
            elif f.type == "str | None":
                defaults[f.name] = None
            elif f.type == "int":
                defaults[f.name] = 0
            elif f.type == "int | None":
                defaults[f.name] = None
            elif f.type == "float":
                defaults[f.name] = 0.0
            elif f.type == "float | None":
                defaults[f.name] = None
            elif f.type == "bool":
                defaults[f.name] = False
            elif f.type == "list | None":
                defaults[f.name] = None
            else:
                defaults[f.name] = None

        defaults["embeddings_provider"] = "google"
        defaults["embeddings_gemini_api_key"] = "test-key"
        defaults["embeddings_gemini_model"] = "gemini-embedding-001"
        defaults["embeddings_gemini_output_dimensionality"] = 768
        defaults["embeddings_vertexai_project_id"] = None
        defaults["embeddings_vertexai_region"] = None
        defaults["embeddings_vertexai_service_account_key"] = None

        defaults.update(overrides)
        return HindsightConfig(**defaults)

    def test_create_with_api_key(self):
        config = self._make_config()
        with patch("hindsight_api.config.get_config", return_value=config):
            emb = create_embeddings_from_env()
        assert isinstance(emb, GeminiEmbeddings)
        assert emb.provider_name == "google"
        assert emb.api_key == "test-key"
        assert emb._is_vertexai is False

    def test_create_with_vertexai(self):
        config = self._make_config(
            embeddings_gemini_api_key=None,
            embeddings_vertexai_project_id="my-project",
            embeddings_vertexai_region="us-east1",
        )
        with patch("hindsight_api.config.get_config", return_value=config):
            emb = create_embeddings_from_env()
        assert isinstance(emb, GeminiEmbeddings)
        assert emb._is_vertexai is True
        assert emb.api_key is None
        assert emb.vertexai_project_id == "my-project"

    def test_create_missing_all_credentials(self):
        config = self._make_config(embeddings_gemini_api_key=None, embeddings_vertexai_project_id=None)
        with patch("hindsight_api.config.get_config", return_value=config):
            with pytest.raises(ValueError, match="is required"):
                create_embeddings_from_env()

    def test_vertexai_takes_priority(self):
        config = self._make_config(embeddings_gemini_api_key="key", embeddings_vertexai_project_id="proj")
        with patch("hindsight_api.config.get_config", return_value=config):
            emb = create_embeddings_from_env()
        assert emb._is_vertexai is True
        assert emb.api_key is None

    def test_create_with_custom_dimensionality(self):
        config = self._make_config(embeddings_gemini_output_dimensionality=256)
        with patch("hindsight_api.config.get_config", return_value=config):
            emb = create_embeddings_from_env()
        assert emb.output_dimensionality == 256
