"""Tests for app/services/rule_engine.py"""

import pytest
from app.services.rule_engine import RuleEngine, PRESET_RULES


class TestKeywordRule:
    """Tests for keyword-type rules."""

    def test_keyword_title_match(self):
        """keyword rule with field='title', article has keyword in title -> True"""
        rule = {"type": "keyword", "field": "title", "keyword": "cancer"}
        matcher = RuleEngine.compile_rule(rule)
        article = {"title": "A novel treatment for cancer", "abstract": "something"}
        assert matcher(article) is True

    def test_keyword_title_no_match(self):
        """keyword not in title -> False"""
        rule = {"type": "keyword", "field": "title", "keyword": "cancer"}
        matcher = RuleEngine.compile_rule(rule)
        article = {"title": "A study on heart disease", "abstract": "some abstract"}
        assert matcher(article) is False

    def test_keyword_abstract_match(self):
        """keyword in abstract -> True"""
        rule = {"type": "keyword", "field": "abstract", "keyword": "biomarker"}
        matcher = RuleEngine.compile_rule(rule)
        article = {"title": "Some title", "abstract": "This biomarker study shows"}
        assert matcher(article) is True

    def test_keyword_abstract_no_match(self):
        """keyword not in abstract -> False"""
        rule = {"type": "keyword", "field": "abstract", "keyword": "biomarker"}
        matcher = RuleEngine.compile_rule(rule)
        article = {"title": "Some title", "abstract": "This study shows nothing"}
        assert matcher(article) is False

    def test_keyword_title_abstract(self):
        """searches both title and abstract"""
        rule = {"type": "keyword", "field": "title_abstract", "keyword": "PD-L1"}
        matcher = RuleEngine.compile_rule(rule)
        # matches in title
        assert matcher({"title": "PD-L1 expression in tumors", "abstract": "nothing"}) is True
        # matches in abstract
        assert matcher({"title": "Nothing", "abstract": "PD-L1 expression was observed"}) is True
        # matches in neither
        assert matcher({"title": "Nothing", "abstract": "nothing here"}) is False

    def test_keyword_field_all(self):
        """field='all' searches title + abstract + journal"""
        rule = {"type": "keyword", "field": "all", "keyword": "oncology"}
        matcher = RuleEngine.compile_rule(rule)
        # matches in journal
        assert matcher({"title": "x", "abstract": "y", "journal": "Journal of Oncology"}) is True
        # matches in title
        assert matcher({"title": "Oncology today", "abstract": "y", "journal": "z"}) is True
        # matches in abstract
        assert matcher({"title": "x", "abstract": "oncology research", "journal": "z"}) is True
        # no match
        assert matcher({"title": "x", "abstract": "y", "journal": "z"}) is False

    def test_keyword_case_sensitive(self):
        """'cancer' matches 'Cancer' when case_sensitive=False, doesn't when True"""
        article = {"title": "Treating Cancer in Patients", "abstract": ""}
        # case-insensitive (default)
        ci_rule = {"type": "keyword", "field": "title", "keyword": "cancer", "case_sensitive": False}
        assert RuleEngine.compile_rule(ci_rule)(article) is True
        # case-sensitive
        cs_rule = {"type": "keyword", "field": "title", "keyword": "cancer", "case_sensitive": True}
        assert RuleEngine.compile_rule(cs_rule)(article) is False
        # case-sensitive with exact case
        cs_exact = {"type": "keyword", "field": "title", "keyword": "Cancer", "case_sensitive": True}
        assert RuleEngine.compile_rule(cs_exact)(article) is True


class TestCompositeRules:
    """Tests for and/or/not compound rules."""

    def test_and_rule(self):
        """both children must match"""
        rule = {
            "type": "and",
            "children": [
                {"type": "keyword", "field": "title", "keyword": "cancer"},
                {"type": "keyword", "field": "abstract", "keyword": "trial"},
            ],
        }
        matcher = RuleEngine.compile_rule(rule)
        # both match
        assert matcher({"title": "cancer study", "abstract": "a clinical trial"}) is True
        # only first matches
        assert matcher({"title": "cancer study", "abstract": "nothing"}) is False
        # only second matches
        assert matcher({"title": "heart study", "abstract": "a clinical trial"}) is False
        # neither matches
        assert matcher({"title": "heart study", "abstract": "nothing"}) is False

    def test_or_rule(self):
        """either child can match"""
        rule = {
            "type": "or",
            "children": [
                {"type": "keyword", "field": "title", "keyword": "cancer"},
                {"type": "keyword", "field": "abstract", "keyword": "trial"},
            ],
        }
        matcher = RuleEngine.compile_rule(rule)
        # both match
        assert matcher({"title": "cancer study", "abstract": "a clinical trial"}) is True
        # first matches
        assert matcher({"title": "cancer study", "abstract": "nothing"}) is True
        # second matches
        assert matcher({"title": "heart study", "abstract": "a clinical trial"}) is True
        # neither matches
        assert matcher({"title": "heart study", "abstract": "nothing"}) is False

    def test_not_rule(self):
        """inverts child result"""
        rule = {
            "type": "not",
            "children": [
                {"type": "keyword", "field": "title", "keyword": "cancer"},
            ],
        }
        matcher = RuleEngine.compile_rule(rule)
        # child matches -> inverted to False
        assert matcher({"title": "cancer study", "abstract": ""}) is False
        # child doesn't match -> inverted to True
        assert matcher({"title": "heart study", "abstract": ""}) is True

    def test_nested_rule(self):
        """and(or(kw1,kw2), kw3) compound"""
        rule = {
            "type": "and",
            "children": [
                {
                    "type": "or",
                    "children": [
                        {"type": "keyword", "field": "title", "keyword": "PD-L1"},
                        {"type": "keyword", "field": "title", "keyword": "CTLA-4"},
                    ],
                },
                {"type": "keyword", "field": "abstract", "keyword": "immunotherapy"},
            ],
        }
        matcher = RuleEngine.compile_rule(rule)
        # PD-L1 + immunotherapy
        assert matcher({"title": "PD-L1 inhibition", "abstract": "immunotherapy trial"}) is True
        # CTLA-4 + immunotherapy
        assert matcher({"title": "CTLA-4 blockade", "abstract": "immunotherapy trial"}) is True
        # neither PD-L1 nor CTLA-4
        assert matcher({"title": "EGFR mutation", "abstract": "immunotherapy trial"}) is False
        # immunotherapy missing
        assert matcher({"title": "PD-L1 inhibition", "abstract": "survival analysis"}) is False


class TestEdgeCases:
    """Edge cases for robustness."""

    def test_empty_article(self):
        """empty string fields don't crash"""
        rule = {"type": "keyword", "field": "title", "keyword": "cancer"}
        matcher = RuleEngine.compile_rule(rule)
        assert matcher({"title": "", "abstract": ""}) is False

    def test_missing_fields(self):
        """article dict missing expected fields returns False gracefully"""
        rule = {"type": "keyword", "field": "title", "keyword": "cancer"}
        matcher = RuleEngine.compile_rule(rule)
        assert matcher({}) is False

        all_matcher = RuleEngine.compile_rule(
            {"type": "keyword", "field": "all", "keyword": "oncology"}
        )
        assert all_matcher({}) is False

    def test_missing_fields_abstract(self):
        """article missing abstract field with field='abstract'"""
        rule = {"type": "keyword", "field": "abstract", "keyword": "cancer"}
        matcher = RuleEngine.compile_rule(rule)
        assert matcher({"title": "cancer study"}) is False

    def test_not_with_no_children(self):
        """not rule with no children defaults to False, inverted to True"""
        rule = {"type": "not", "children": []}
        matcher = RuleEngine.compile_rule(rule)
        assert matcher({"title": "anything"}) is True


class TestCompileMultiRule:
    """Tests for compile_multi_rule."""

    def test_compile_multi_rule_any(self):
        """match_mode='any', one matcher matches -> True"""
        rules = [
            {"type": "keyword", "field": "title", "keyword": "cancer"},
            {"type": "keyword", "field": "abstract", "keyword": "biomarker"},
        ]
        matcher = RuleEngine.compile_multi_rule(rules, match_mode="any")
        assert matcher({"title": "cancer study", "abstract": "nothing"}) is True
        assert matcher({"title": "heart study", "abstract": "a biomarker"}) is True
        assert matcher({"title": "heart study", "abstract": "nothing"}) is False

    def test_compile_multi_rule_all(self):
        """match_mode='all', one fails -> False"""
        rules = [
            {"type": "keyword", "field": "title", "keyword": "cancer"},
            {"type": "keyword", "field": "abstract", "keyword": "trial"},
        ]
        matcher = RuleEngine.compile_multi_rule(rules, match_mode="all")
        # both match
        assert matcher({"title": "cancer study", "abstract": "a trial"}) is True
        # only first matches
        assert matcher({"title": "cancer study", "abstract": "nothing"}) is False
        # only second matches
        assert matcher({"title": "heart study", "abstract": "a trial"}) is False

    def test_compile_multi_rule_empty(self):
        """empty rules list -> always True"""
        matcher = RuleEngine.compile_multi_rule([], match_mode="any")
        assert matcher({"title": "anything"}) is True
        assert matcher({}) is True


class TestPresetRules:
    """Tests for PRESET_RULES predefined templates."""

    def test_preset_drug_trial(self):
        """PRESET_RULES['drug_trial'] compiles and matches a trial article"""
        rule_def = PRESET_RULES["drug_trial"]["rule"]
        matcher = RuleEngine.compile_rule(rule_def)
        # article containing 'trial' and 'phase'
        article = {"title": "A phase III trial of pembrolizumab", "abstract": "clinical results"}
        assert matcher(article) is True
        # article with 'trial' but no phase/randomi
        article2 = {"title": "A trial of something", "abstract": "just a study"}
        # 'trial' in title_abstract but no 'phase' or 'randomi' in title_abstract
        assert matcher(article2) is False
        # no trial at all
        article3 = {"title": "Review article", "abstract": "summarizing literature"}
        assert matcher(article3) is False

    def test_preset_chinese_population(self):
        """matches article about 'China'"""
        rule_def = PRESET_RULES["chinese_population"]["rule"]
        matcher = RuleEngine.compile_rule(rule_def)
        # 'China' in journal
        assert matcher({"title": "x", "abstract": "y", "journal": "China Medical"}) is True
        # 'Chinese' in title_abstract
        assert matcher({"title": "Chinese patients", "abstract": "y"}) is True
        # 'Han Chinese' only in title_abstract (field is title_abstract)
        assert matcher({"title": "Han Chinese population", "abstract": ""}) is True
        # no match
        assert matcher({"title": "US patients", "abstract": "American cohort"}) is False

    def test_preset_biomarker(self):
        """matches article mentioning 'biomarker'"""
        rule_def = PRESET_RULES["biomarker_study"]["rule"]
        matcher = RuleEngine.compile_rule(rule_def)
        # 'biomarker' in title_abstract
        assert matcher({"title": "Novel biomarker for cancer", "abstract": ""}) is True
        # 'ctDNA' in abstract
        assert matcher({"title": "", "abstract": "ctDNA analysis shows"}) is True
        # 'PD-L1 expression' in abstract
        assert matcher({"title": "", "abstract": "PD-L1 expression was detected"}) is True
        # no match
        assert matcher({"title": "Surgery outcomes", "abstract": "standard care"}) is False

    def test_preset_survival_endpoint(self):
        """matches article mentioning 'overall survival' + 'HR'"""
        rule_def = PRESET_RULES["survival_endpoint"]["rule"]
        matcher = RuleEngine.compile_rule(rule_def)
        # 'overall survival' + 'HR ' (note trailing space)
        article = {"title": "Overall survival analysis", "abstract": "HR 0.75 (0.6-0.9)"}
        assert matcher(article) is True
        # 'overall survival' + 'hazard ratio'
        article2 = {"title": "Overall survival data", "abstract": "hazard ratio 0.8"}
        assert matcher(article2) is True
        # 'overall survival' + 'median'
        article3 = {"title": "Overall survival results", "abstract": "median 24.5 months"}
        assert matcher(article3) is True
        # 'overall survival' only, no secondary keyword
        article4 = {"title": "Overall survival analysis", "abstract": "just data"}
        assert matcher(article4) is False
        # no match at all
        article5 = {"title": "Side effects", "abstract": "toxicity profile"}
        assert matcher(article5) is False

    def test_preset_novel_target(self):
        """matches article with 'novel' in title"""
        rule_def = PRESET_RULES["novel_target"]["rule"]
        matcher = RuleEngine.compile_rule(rule_def)
        # 'novel' in title
        assert matcher({"title": "A novel therapeutic target", "abstract": ""}) is True
        # 'mechanism' in title
        assert matcher({"title": "Mechanism of action of drug X", "abstract": ""}) is True
        # no match
        assert matcher({"title": "Standard treatment review", "abstract": ""}) is False
