from typing import Generator, List
from unittest.mock import MagicMock, patch

import numpy as np
import pytest

from semantica.embeddings.embedding_generator import EmbeddingGenerator
from semantica.embeddings.graph_embedding_manager import GraphEmbeddingManager
from semantica.embeddings.pooling_strategies import PoolingStrategyFactory
from semantica.embeddings.text_embedder import TextEmbedder


# Infra Mocks
@pytest.fixture(autouse=True)
def kill_io_overhead():
    """Silences logging and tracker globally."""
    with patch("semantica.utils.logging.get_logger"), patch(
        "semantica.utils.progress_tracker.get_progress_tracker"
    ) as mock_tracker:

        tracker = MagicMock()
        tracker.enabled = False
        tracker._start_tracking.return_value = "dummy_id"
        mock_tracker.return_value = tracker

        with patch(
            "semantica.embeddings.text_embedder.get_progress_tracker",
            return_value=tracker,
        ):
            yield


# __ Model Mocks __


class MockSentenceTransformer:
    """
    Simulates ST.encode without loading the fat model itself.
    """

    def __init__(self, dim=384):
        self.dim = dim

    def encode(
        self, sentences: List[str], normalize_embeddings=True, **kwargs
    ) -> np.ndarray:
        count = len(sentences)
        return np.random.rand(count, self.dim).astype(np.float32)

    def get_sentence_embedding_dimension(self):
        return self.dim


class MockFastEmbed:
    """
    Simulates FastEmbed.embed generator behavior.
    """

    def __init__(self, dim=384):
        self.dim = dim

    def embed(self, documents: List[str]) -> Generator[np.ndarray, None, None]:
        for _ in documents:
            yield np.random.rand(self.dim).astype(np.float32)


# ~~ Fixtures ~~
@pytest.fixture
def text_embedder_st():
    """
    Text embedder configured with SentenceTransformer
    """
    embedder = TextEmbedder(method="sentence_transformers", model_name="mock-bert")
    embedder.model = MockSentenceTransformer()
    embedder.progress_tracker = MagicMock()
    embedder.progress_tracker.enabled = False

    return embedder


@pytest.fixture
def text_embedder_fast():
    """
    Text Embedder cofnigures with Mock FastEmbed.
    """

    embedder = TextEmbedder(method="fastembed", model_name="mock-bge")
    embedder.fastembed_model = MockFastEmbed()
    embedder.progress_tracker = MagicMock()
    embedder.progress_tracker.enabled = False
    return embedder


# ~~ Benchmarks


@pytest.mark.parametrize("strategy", ["mean", "max", "cls", "attention"])
def test_pooling_math_speed(benchmark, strategy):
    """
    Measures the raw NumPy speed of pooling strategies.
    Scenario: Pooling a batch of 128 token embeddings.
    """

    embeddings = np.random.rand(128, 768).astype(np.float32)
    pooler = PoolingStrategyFactory.create(strategy)

    benchmark.pedantic(lambda: pooler.pool(embeddings), iterations=1000, rounds=100)


def test_hierarchical_pooling_overhead(benchmark):
    """
    Measures the overhead of two-step hierarchical pooling.
    """

    embeddings = np.random.rand(1000, 768).astype(np.float32)
    pooler = PoolingStrategyFactory.create("hierarchical", chunk_size=100)

    benchmark.pedantic(lambda: pooler.pool(embeddings), iterations=500, rounds=50)


def test_st_wrapper_overhead(benchmark, text_embedder_st):
    """
    Measures overhead of TextEmbedder wrapper around SentenceTransformers.
    """

    text = "This is a whatever we are doing here since idk"

    benchmark.pedantic(
        lambda: text_embedder_st.embed_text(text), iterations=1000, rounds=20
    )


def test_fastembed_generator_consumption(benchmark, text_embedder_fast):
    """
    Measures the cost of consuming the FastEmbed generator
    and converting to Array.
    """
    texts = [f"Sentence {i}" for i in range(20)]

    benchmark.pedantic(
        lambda: text_embedder_fast.embed_batch(texts), iterations=100, rounds=20
    )


@pytest.mark.parametrize("batch_size", [10, 100, 1000])
def test_batch_processing_pipeline(benchmark, batch_size, text_embedder_st):
    """
    Measures the full EmbeddingGenerator pipeline:
    Input validation -> Type detection -> Batching -> Mock Model -> Error handling.
    """

    generator = EmbeddingGenerator()

    generator.text_embedder = text_embedder_st
    generator.progress_tracker = MagicMock()
    generator.progress_tracker.enabled = False

    data = [f"Item {i}" for i in range(batch_size)]

    benchmark.pedantic(lambda: generator.process_batch(data), iterations=5, rounds=10)


@pytest.mark.parametrize("count", [100, 1000])
def test_graph_embedding_prep(benchmark, count, text_embedder_st):
    """
    Measures how fast we can reshape dict for GraphDBs
    """
    manager = GraphEmbeddingManager()
    manager.embedding_generator.text_embedder = text_embedder_st

    manager.embedding_generator.generate_embeddings = MagicMock(
        return_value=np.random.rand(count, 384).astype(np.float32)
    )

    entities = [{"id": f"e{i}", "text": f"Entity{i}"} for i in range(count)]

    def op():
        return manager.prepare_for_graph_db(entities, backend="neo4j")

    benchmark.pedantic(op, iterations=10, rounds=10)
