"""Tests for data integrity of biosynoynms database."""

import tempfile
import unittest
from collections import Counter
from pathlib import Path

import bioregistry
from curies import NamedReference, Reference, ReferenceTuple
from curies import vocabulary as v

import biosynonyms
from biosynonyms.model import Synonym, _sort_key
from biosynonyms.resources import (
    NEGATIVES_PATH,
    POSITIVES_PATH,
    SYNONYM_PREDICATE_CURIES,
    UNENTITIES_PATH,
    _unentities_key,
)


class TestIntegrity(unittest.TestCase):
    """Test case for data integrity tests."""

    def assert_curie(self, curie: str):
        """Assert a CURIE is standardized against the Bioregistry.

        :param curie: A compact uniform resource identifier
            of the form ``<prefix>:<identifier>``.
        """
        prefix, identifier = ReferenceTuple.from_curie(curie)

        norm_prefix = bioregistry.normalize_prefix(prefix)
        self.assertIsNotNone(norm_prefix)
        self.assertEqual(norm_prefix, prefix)

        pattern = bioregistry.get_pattern(prefix)
        if pattern:
            self.assertRegex(identifier, pattern)

    def test_positives(self):
        """Test each row in the positives file."""
        with POSITIVES_PATH.open() as file:
            _, *rows = (tuple(line.strip().split("\t")) for line in file)

        for row_index, row in enumerate(rows, start=1):
            with self.subTest(row=row_index):
                self.assertEqual(11, len(row))
                (
                    text,
                    curie,
                    _name,
                    predicate,
                    synonym_type,
                    references,
                    contributor_curie,
                    date,
                    _lang,
                    _comment,
                    _src,
                ) = row
                self.assertLess(1, len(text), msg="can not have 1 letter synonyms")
                self.assert_curie(curie)
                self.assertIn(predicate, SYNONYM_PREDICATE_CURIES)
                if synonym_type:
                    self.assertTrue(synonym_type.startswith("OMO:"))
                for reference in references.split(",") if references else []:
                    reference = reference.strip()
                    self.assert_curie(reference)
                self.assert_curie(contributor_curie)
                if date:
                    self.assertRegex(date, "\\d{4}-\\d{2}-\\d{2}")

        # test sorted
        self.assertEqual(sorted(rows, key=_sort_key), rows, msg="synonyms are not properly sorted")

        # test no duplicates
        c = Counter(row[:2] for row in rows)
        duplicated = {key: count for key, count in c.items() if count > 1}
        self.assertEqual(0, len(duplicated), msg=f"duplicated entries: {duplicated}")

    def test_negatives(self):
        """Test each row in the negatives file."""
        with NEGATIVES_PATH.open() as file:
            _, *rows = (tuple(line.strip().split("\t")) for line in file)

        for row_index, row in enumerate(rows, start=1):
            with self.subTest(row=row_index):
                self.assertEqual(5, len(row))
                text, curie, _name, references, contributor_curie = row
                self.assertLess(1, len(text), msg="can not have 1 letter synonyms")
                self.assert_curie(curie)
                for reference in references.split(","):
                    reference = reference.strip()
                    self.assert_curie(reference)
                self.assert_curie(contributor_curie)

        # test sorted
        self.assertEqual(
            sorted(rows, key=_sort_key),
            rows,
            msg="negative synonyms are not properly sorted",
        )

        # test no duplicates
        c = Counter(row[:2] for row in rows)
        duplicated = {key: count for key, count in c.items() if count > 1}
        self.assertEqual(0, len(duplicated), msg=f"duplicated entries: {duplicated}")

    def test_non_entities(self):
        """Test each row of the non-entities file."""
        with UNENTITIES_PATH.open() as file:
            _header, *rows = (line.strip().split("\t") for line in file)
        self.assertEqual(sorted(rows, key=_unentities_key), rows)
        for line_number, line in enumerate(rows, start=1):
            with self.subTest(line_number=line_number):
                self.assertEqual(2, len(line))
                text, orcid = line
                self.assertEqual(text.strip(), text)
                self.assertTrue(bioregistry.is_valid_identifier("orcid", orcid))

    def test_gilda(self):
        """Test getting tilda terms."""
        grounder = biosynonyms.get_grounder()
        scored_matches = grounder.ground("YAL021C")
        self.assertEqual(1, len(scored_matches))
        self.assertEqual("sgd", scored_matches[0].term.db)
        self.assertEqual("S000000019", scored_matches[0].term.id)

        reference = NamedReference.from_curie("test:1", "test")
        label = Reference.from_curie("rdfs:label")
        synonym_1 = Synonym(
            text="tests", predicate=v.has_exact_synonym, type=v.plural_form, reference=reference
        )
        gilda_term_1 = synonym_1.to_gilda()
        self.assertEqual("synonym", gilda_term_1.status)

        synonym_2 = Synonym(text="test", predicate=label, reference=reference)
        gilda_term_2 = synonym_2.to_gilda()
        self.assertEqual("name", gilda_term_2.status)

        synonym_3 = Synonym(
            text="old test",
            predicate=v.has_exact_synonym,
            reference=reference,
            type=v.previous_name,
        )
        gilda_term_3 = synonym_3.to_gilda()
        self.assertEqual("former_name", gilda_term_3.status)

    def test_model(self):
        """Test loading the data model."""
        biosynonyms.get_positive_synonyms()
        biosynonyms.get_negative_synonyms()

    def test_io_roundtrip(self) -> None:
        """Test IO roundtrip."""
        synonyms = biosynonyms.get_positive_synonyms()[:3]  # sample just a few

        with tempfile.TemporaryDirectory() as d:
            path = Path(d).joinpath("test.tsv")
            biosynonyms.write_synonyms(path, synonyms)
            reloaded_synonyms = biosynonyms.parse_synonyms(path)

        self.assertEqual(synonyms, reloaded_synonyms)
