import json
import re
from pathlib import Path

import pytest
from dirty_equals import HasLen, IsPartialDict, IsStr
from pymultirole_plugins.v1.schema import AltText, Annotation, Document, Term

from pyprocessors_document_fingerprint.document_fingerprint import (
    DocumentFingerprintParameters,
    DocumentFingerprintProcessor,
    by_label,
    by_lexicon,
    group_annotations,
    has_knowledge,
)


def test_model():
    model = DocumentFingerprintProcessor.get_model()
    model_class = model.model_construct().__class__
    assert model_class == DocumentFingerprintParameters


# Arrange
@pytest.fixture
def original_doc():
    testdir = Path(__file__).parent
    source = Path(testdir, "data/afp_ner_fr-document-test.json")
    with source.open("r") as fin:
        doc = json.load(fin)
        original_doc = Document(**doc)
        return original_doc


# Arrange
@pytest.fixture
def original_doc_en():
    testdir = Path(__file__).parent
    source = Path(testdir, "data/afp_ner_en-document-test.json")
    with source.open("r") as fin:
        doc = json.load(fin)
        original_doc = Document(**doc)
        return original_doc


def test_document_fingerprint_linker(original_doc):
    # linker
    doc = original_doc.model_copy(deep=True)
    processor = DocumentFingerprintProcessor()
    parameters = DocumentFingerprintParameters()
    docs = processor.process([doc], parameters)
    conso: Document = docs[0]
    assert conso.altTexts == HasLen(1)
    altText = conso.altTexts[0]
    FINGERPRINT = re.compile(r"([QE]\d+[ ]?)+")
    assert altText.model_dump() == IsPartialDict(name="fingerprint", text=IsStr(regex=FINGERPRINT))


def test_document_fingerprint_linker_en(original_doc_en):
    # linker
    doc = original_doc_en.model_copy(deep=True)
    processor = DocumentFingerprintProcessor()
    parameters = DocumentFingerprintParameters()
    docs = processor.process([doc], parameters)
    conso: Document = docs[0]
    assert conso.altTexts == HasLen(1)
    altText = conso.altTexts[0]
    FINGERPRINT = re.compile(r"([QE]\d+[ ]?)+")
    assert altText.model_dump() == IsPartialDict(name="fingerprint", text=IsStr(regex=FINGERPRINT))


def test_no_annotations():
    """Document with no annotations produces no altTexts."""
    doc = Document(text="hello world")
    processor = DocumentFingerprintProcessor()
    parameters = DocumentFingerprintParameters()
    docs = processor.process([doc], parameters)
    assert docs[0].altTexts is None


def test_custom_alttext_name(original_doc):
    """Custom as_altText name is used instead of default 'fingerprint'."""
    doc = original_doc.model_copy(deep=True)
    processor = DocumentFingerprintProcessor()
    parameters = DocumentFingerprintParameters(as_altText="custom_fp")
    docs = processor.process([doc], parameters)
    conso: Document = docs[0]
    assert conso.altTexts == HasLen(1)
    assert conso.altTexts[0].name == "custom_fp"


def test_empty_alttext_name_skips(original_doc):
    """Empty as_altText disables fingerprint generation."""
    doc = original_doc.model_copy(deep=True)
    processor = DocumentFingerprintProcessor()
    parameters = DocumentFingerprintParameters(as_altText="")
    docs = processor.process([doc], parameters)
    assert docs[0].altTexts is None or docs[0].altTexts == []


def test_existing_alttext_replaced(original_doc):
    """Existing altText with the same name is replaced, others are kept."""
    doc = original_doc.model_copy(deep=True)
    doc.altTexts = [
        AltText(name="fingerprint", text="old_value"),
        AltText(name="other", text="keep_me"),
    ]
    processor = DocumentFingerprintProcessor()
    parameters = DocumentFingerprintParameters()
    docs = processor.process([doc], parameters)
    conso: Document = docs[0]
    names = [a.name for a in conso.altTexts]
    assert "other" in names
    fp = next(a for a in conso.altTexts if a.name == "fingerprint")
    assert fp.text != "old_value"


def test_has_knowledge():
    ann_with_terms = Annotation(start=0, end=5, terms=[Term(identifier="Q1", lexicon="wikidata")])
    ann_no_terms = Annotation(start=0, end=5)
    assert has_knowledge(ann_with_terms) is True
    assert has_knowledge(ann_no_terms) is False


def test_by_lexicon():
    ann = Annotation(start=0, end=5, terms=[Term(identifier="Q1", lexicon="wikidata")])
    assert by_lexicon(ann) == "wikidata"
    ann_no_terms = Annotation(start=0, end=5)
    assert by_lexicon(ann_no_terms) == ""


def test_by_label():
    ann = Annotation(start=0, end=5, labelName="my_label")
    assert by_label(ann) == "my_label"


def test_group_annotations(original_doc_en):
    """group_annotations groups by lexicon key."""
    groups = group_annotations(original_doc_en, by_lexicon)
    assert "wikidata" in groups or "eurovoc" in groups
