"""PubMed 查询语法解析器测试"""

import pytest

from app.services.pubmed_query_parser import (
    ParsedPubmedQuery,
    parse_pubmed_query,
    is_pubmed_syntax,
    tokenise,
    extract_pubmed_query_for_prisma,
)


class TestIsPubmedSyntax:
    def test_plain_text_not_pubmed(self):
        assert not is_pubmed_syntax("lung cancer")

    def test_field_tag_detected(self):
        assert is_pubmed_syntax('"lung cancer"[TI]')

    def test_boolean_and_detected(self):
        assert is_pubmed_syntax("cancer AND therapy")

    def test_boolean_or_detected(self):
        assert is_pubmed_syntax("cancer OR tumor")

    def test_boolean_not_detected(self):
        assert is_pubmed_syntax("cancer NOT review")

    def test_empty_not_pubmed(self):
        assert not is_pubmed_syntax("")
        assert not is_pubmed_syntax(None)

    def test_lowercase_boolean_not_detected(self):
        """小写的 and/or/not 不作为 PubMed 语法标记"""
        assert not is_pubmed_syntax("cancer and therapy")


class TestTokenise:
    def test_simple_tokens(self):
        tokens = tokenise('"lung cancer"[TI]')
        texts = [t.value for t in tokens if t.value]
        assert '"lung cancer"' in texts
        assert "[TI]" in texts

    def test_boolean_tokens(self):
        tokens = tokenise("a AND b OR c NOT d")
        texts = [t.value for t in tokens if t.value]
        assert "AND" in texts
        assert "OR" in texts
        assert "NOT" in texts

    def test_parentheses(self):
        tokens = tokenise("(a OR b)")
        types = [t.type.name for t in tokens]
        assert "LPAREN" in types
        assert "RPAREN" in types


class TestParsePubmedQuery:
    def test_plain_text_no_field(self):
        """纯文本查询返回 plain_terms"""
        result = parse_pubmed_query("lung cancer")
        assert len(result.plain_terms) == 2
        assert result.plain_terms[0].text == "lung"
        assert result.plain_terms[1].text == "cancer"

    def test_title_field(self):
        """[TI] 映射到 title 字段"""
        result = parse_pubmed_query('"lung cancer"[TI]')
        assert len(result.title_terms) == 1
        t = result.title_terms[0]
        assert t.text == "lung cancer"
        assert t.exact is True
        assert t.field == "title"

    def test_abstract_field(self):
        result = parse_pubmed_query('"cancer"[AB]')
        assert len(result.abstract_terms) == 1
        assert result.abstract_terms[0].text == "cancer"

    def test_tiab_field(self):
        result = parse_pubmed_query('"cancer"[TIAB]')
        assert len(result.tiab_terms) == 1
        assert result.tiab_terms[0].text == "cancer"

    def test_author_field(self):
        result = parse_pubmed_query("smith j[AU]")
        assert len(result.author_terms) == 1
        assert result.author_terms[0].text == "j"
        assert result.author_terms[0].field == "author"
        # smith should be captured as plain term via implicit AND
        assert len(result.plain_terms) >= 1

    def test_journal_field(self):
        result = parse_pubmed_query('"nature"[TA]')
        assert len(result.journal_terms) == 1
        assert result.journal_terms[0].text == "nature"

    def test_mesh_field(self):
        result = parse_pubmed_query('"lung neoplasms"[MH]')
        assert len(result.mesh_terms) == 1
        assert result.mesh_terms[0] == "lung neoplasms"

    def test_majr_field(self):
        result = parse_pubmed_query('"lung neoplasms"[MAJR]')
        assert len(result.majr_terms) == 1
        assert result.majr_terms[0] == "lung neoplasms"

    def test_pub_type(self):
        result = parse_pubmed_query('"review"[PT]')
        assert len(result.pub_types) == 1
        assert result.pub_types[0] == "review"

    def test_pmid_field(self):
        result = parse_pubmed_query("12345678[PMID]")
        assert len(result.pmid_terms) == 1
        assert result.pmid_terms[0] == 12345678

    def test_doi_field(self):
        result = parse_pubmed_query('"10.1000/test"[DOI]')
        assert len(result.doi_terms) == 1
        assert "10.1000/test" in result.doi_terms[0]

    def test_date_range_dp(self):
        result = parse_pubmed_query("2024:2026[DP]")
        assert result.year_from == 2024
        assert result.year_to == 2026

    def test_simple_and(self):
        result = parse_pubmed_query("cancer AND therapy")
        assert result.boolean_operator == "and"
        assert len(result.plain_terms) == 2

    def test_simple_or(self):
        result = parse_pubmed_query("cancer OR tumor")
        assert result.boolean_operator == "or"
        assert len(result.plain_terms) == 2

    def test_boolean_not(self):
        result = parse_pubmed_query('"lung"[TI] NOT "review"[PT]')
        assert result.has_not is True
        assert len(result.not_terms) == 1
        assert result.not_terms[0].text == "review"

    def test_complex_nested(self):
        result = parse_pubmed_query(
            '("lung cancer"[TI] OR "breast cancer"[TI]) AND "immunotherapy"[TIAB]'
        )
        assert len(result.title_terms) == 2
        assert len(result.tiab_terms) == 1
        assert result.boolean_operator == "mixed"

    def test_implicit_and_multiple_terms(self):
        result = parse_pubmed_query("a b c")
        assert len(result.plain_terms) == 3

    def test_lowercase_field_tag(self):
        result = parse_pubmed_query('"lung cancer"[ti]')
        assert len(result.title_terms) == 1

    def test_mixedcase_field_tag(self):
        result = parse_pubmed_query('"lung cancer"[Ti]')
        assert len(result.title_terms) == 1

    def test_quoted_phrase_exact(self):
        result = parse_pubmed_query('"exact phrase"')
        assert len(result.plain_terms) == 1
        assert result.plain_terms[0].exact is True

    def test_not_with_parens(self):
        result = parse_pubmed_query('("cancer" AND "therapy") NOT "review"')
        assert result.has_not is True

    def test_empty_query(self):
        result = parse_pubmed_query("")
        assert len(result.plain_terms) == 0

    def test_invalid_syntax_degrades(self):
        """非法语法退化到空 ParsedPubmedQuery"""
        result = parse_pubmed_query("invalid[[syntax")
        # Should not crash, return empty plain_terms
        assert isinstance(result, ParsedPubmedQuery)

    def test_field_with_dot(self):
        result = parse_pubmed_query("john.doe[AU]")
        assert len(result.author_terms) == 1


class TestExtractForPrisma:
    def test_extract_mesh_and_normalize(self):
        normalized, mesh = extract_pubmed_query_for_prisma(
            '("lung cancer"[ti] OR "lung neoplasms"[mh]) AND 2024:2026[dp]'
        )
        # Field tags should be uppercased
        assert "[TI]" in normalized or "[ti]" not in normalized

    def test_pubmed_query_types(self):
        normalized, mesh = extract_pubmed_query_for_prisma(
            '"cancer"[TI] AND "immunotherapy"[MH]'
        )
        assert "cancer" in normalized
        assert "immunotherapy" in normalized
        assert "immunotherapy" in mesh
