#!/usr/bin/env python3
"""
Unit tests for verify_citation's verdict aggregation logic -- this is the
anti-hallucination guarantee, so its three-way verified/suspect/unverified
decision needs to stay correct independent of any real network call. All
individual check_* functions are mocked; only the aggregation logic itself
(verify() and _combine_title_checks()) is under test.

Run: python -m unittest discover -s .claude/skills/literature-search-verify/scripts
"""
import os
import sys
import unittest
from unittest import mock

sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
import verify_citation as vc


class TestCombineTitleChecks(unittest.TestCase):
    """Two independent title sources (S2 + OpenAlex) shouldn't let one
    source's incomplete coverage outvote the other's confirmation."""

    def test_both_pass_picks_higher_similarity(self):
        s2 = {"status": "pass", "similarity": 0.95, "matched_title": "A"}
        oa = {"status": "pass", "similarity": 0.99, "matched_title": "B"}
        combined = vc._combine_title_checks(s2, oa)
        self.assertEqual(combined["status"], "pass")
        self.assertEqual(combined["matched_title"], "B")

    def test_one_pass_one_fail_still_passes(self):
        s2 = {"status": "fail", "reason": "no matching title found on Semantic Scholar"}
        oa = {"status": "pass", "similarity": 0.95, "matched_title": "B"}
        self.assertEqual(vc._combine_title_checks(s2, oa)["status"], "pass")

    def test_one_pass_one_skipped_still_passes(self):
        s2 = {"status": "skipped", "reason": "timeout"}
        oa = {"status": "pass", "similarity": 0.95, "matched_title": "B"}
        self.assertEqual(vc._combine_title_checks(s2, oa)["status"], "pass")

    def test_both_fail_is_fail(self):
        s2 = {"status": "fail", "reason": "x"}
        oa = {"status": "fail", "reason": "y"}
        self.assertEqual(vc._combine_title_checks(s2, oa)["status"], "fail")

    def test_fail_plus_skipped_is_still_fail(self):
        s2 = {"status": "fail", "reason": "x"}
        oa = {"status": "skipped", "reason": "y"}
        self.assertEqual(vc._combine_title_checks(s2, oa)["status"], "fail")

    def test_both_skipped_is_skipped_not_fail(self):
        s2 = {"status": "skipped", "reason": "x"}
        oa = {"status": "skipped", "reason": "y"}
        self.assertEqual(vc._combine_title_checks(s2, oa)["status"], "skipped")


class TestVerifyVerdict(unittest.TestCase):
    def test_all_checks_pass_is_verified(self):
        with mock.patch.object(vc, "check_arxiv_id", return_value={"status": "pass"}), \
             mock.patch.object(vc, "check_doi", return_value={"status": "pass"}), \
             mock.patch.object(vc, "check_title_cross_source_s2", return_value={"status": "pass", "similarity": 1.0}), \
             mock.patch.object(vc, "check_title_cross_source_openalex", return_value={"status": "pass", "similarity": 1.0}):
            result = vc.verify(title="T", arxiv_id="1234.5678", doi="10.1/x")
        self.assertEqual(result["verdict"], "verified")

    def test_one_failing_check_is_suspect_even_if_another_passes(self):
        with mock.patch.object(vc, "check_arxiv_id", return_value={"status": "fail", "reason": "arXiv ID not found"}), \
             mock.patch.object(vc, "check_doi", return_value={"status": "pass"}):
            result = vc.verify(arxiv_id="9999.99999", doi="10.1/x")
        self.assertEqual(result["verdict"], "suspect")

    def test_all_checks_skipped_is_unverified_not_verified(self):
        with mock.patch.object(vc, "check_arxiv_id", return_value={"status": "skipped", "reason": "timeout"}):
            result = vc.verify(arxiv_id="1234.5678")
        self.assertEqual(result["verdict"], "unverified")

    def test_no_identifiers_given_produces_no_checks_and_unverified(self):
        result = vc.verify(title=None, arxiv_id=None, doi=None)
        self.assertEqual(result["checks"], {})
        self.assertEqual(result["verdict"], "unverified")


class TestTitleSimilarity(unittest.TestCase):
    def test_identical_titles_score_one(self):
        self.assertEqual(vc.title_similarity("Same Title", "same title"), 1.0)

    def test_empty_or_missing_inputs_score_zero(self):
        self.assertEqual(vc.title_similarity("", "x"), 0.0)
        self.assertEqual(vc.title_similarity(None, "x"), 0.0)
        self.assertEqual(vc.title_similarity("x", None), 0.0)


if __name__ == "__main__":
    unittest.main()
