from unittest.mock import patch

import pytest

from semantica.semantic_extract.ner_extractor import Entity, NERExtractor
from semantica.semantic_extract.semantic_analyzer import SemanticAnalyzer


# Fixtures
@pytest.fixture
def document_batch():
    base = "The quick brown fox jumps over the lazy dog."
    docs = [
        f"{base} Variation {i}. Apple Inc released a product in 2024."
        for i in range(50)
    ]
    return docs


# Fast wrapper-only benchmark (always runs)
def test_ner_ml_wrapper_overhead(benchmark, long_text_string):
    extractor = NERExtractor(method="ml", model="en_core_web_sm")

    entity_text = "Semantica"
    phrase = f"{entity_text} is a knowledge graph framework. "
    medium_text = phrase * 5

    expected_entities = []
    phrase_len = len(phrase)
    for i in range(5):
        start = i * phrase_len
        end = start + len(entity_text)
        ent = Entity(
            text=entity_text,
            label="ORG",
            start_char=start,
            end_char=end,
            confidence=0.98,
            metadata={"lemma": entity_text},
        )
        expected_entities.append(ent)

    def custom_ml_extraction(text: str, **method_options):
        min_confidence = method_options.get("min_confidence", 0.5)
        entity_types = method_options.get("entity_types")
        filtered = []
        for ent in expected_entities:
            if entity_types and ent.label not in entity_types:
                continue
            if ent.confidence >= min_confidence:
                filtered.append(ent)
        return filtered

    with patch(
        "semantica.semantic_extract.methods.get_entity_method"
    ) as mock_get_method:
        mock_get_method.side_effect = lambda name: (
            custom_ml_extraction if name == "ml" else (lambda t, **o: [])
        )

        def op():
            return extractor.extract_entities(text=medium_text)

        result = benchmark.pedantic(op, rounds=20, iterations=5)

    assert len(result) == 5
    assert all(e.text == "Semantica" for e in result)
    assert all(e.label == "ORG" for e in result)
    assert all(e.confidence == 0.98 for e in result)
    assert all(medium_text[e.start_char : e.end_char] == e.text for e in result)


# Real spaCy benchmark
@pytest.mark.benchmark(group="ner_real_ml")
def test_ner_ml_real_performance(benchmark, long_text_string):
    """
    Full spaCy inference + wrapper overhead.
    Only runs when real spaCy is loaded (BENCHMARK_REAL_LIBS=1).
    """
    extractor = NERExtractor(method="ml", model="en_core_web_sm")

    if (
        extractor.nlp is None
        or not hasattr(extractor.nlp, "pipe_names")
        or "ner" not in extractor.nlp.pipe_names
    ):
        pytest.skip(
            "Real spaCy NER pipeline not available — skipping production benchmark"
        )

    medium_text = long_text_string[:10000]

    medium_text += " Apple Inc. was founded by Steve Jobs and Steve Wozniak in Cupertino, California on April 1, 1976. Microsoft is a competitor."

    def op():
        return extractor.extract_entities(text=medium_text)

    result = benchmark.pedantic(op, rounds=6, iterations=2)

    assert len(result) >= 6
    assert any("Apple" in e.text and e.label == "ORG" for e in result)
    assert any(e.label == "PERSON" for e in result)
    assert any(e.label in {"GPE", "LOC"} for e in result)
    assert any(e.label == "DATE" for e in result)
    assert any("Microsoft" in e.text and e.label == "ORG" for e in result)


def test_ner_pattern_speed(benchmark, long_text_string):
    extractor = NERExtractor(method="pattern")
    medium_text = long_text_string[:50000]
    text_with_entities = medium_text + " Apple Inc. was founded in 1976. "

    def op():
        return extractor.extract_entities(text=text_with_entities)

    result = benchmark.pedantic(op, rounds=20, iterations=5)
    assert len(result) > 0
    assert result[0].label in ["ORG", "DATE", "UNKNOWN"]


def test_ner_batch_throughput(benchmark, document_batch):
    extractor = NERExtractor(method="pattern")

    def run_batch():
        return extractor.extract_entities_batch(document_batch, max_workers=2)

    result = benchmark.pedantic(run_batch, rounds=10, iterations=5)
    assert len(result) == len(document_batch)
    assert len(result[0]) > 0


def test_similarity_calculation(benchmark):
    analyzer = SemanticAnalyzer()
    text1 = "The quick brown fox jumps over the lazy dog" * 10
    text2 = "The slow brown fox jumped over the sleeping dog" * 10

    def op():
        return analyzer.calculate_similarity(text1, text2, method="jaccard")

    result = benchmark.pedantic(op, rounds=100, iterations=100)
    assert 0.0 <= result <= 1.0


def test_clustering_algorithm(benchmark, document_batch):
    analyzer = SemanticAnalyzer()
    options = {"similarity_threshold": 0.1}

    def op():
        return analyzer.cluster_semantically(texts=document_batch, **options)

    result = benchmark.pedantic(op, rounds=10, iterations=5)
    assert len(result) > 0
    assert result[0].texts
