"""Runnable companion to 'The Right Answer About the Wrong Place'.

Run: python3 identity_gate_demo.py  (Python 3.10+, standard library only)

This is an educational policy model, NOT Audiala's production module and NOT
an entity resolver. Historical behavior is a reduced transcription of the
September 10 mismatch guard. The stricter three-way policy is a recommendation.
Inputs use fabricated IDs in one already-resolved namespace. The caller must
obtain source_id independently from source metadata, not copy target_id into it.
Tests cover decision boundaries, not HTTP retries, real source accuracy, or
whole-pipeline publication safety. No network access or external writes.
"""
from enum import Enum
import re
import unittest


class Decision(str, Enum):
    MATCH = "match"
    MISMATCH = "mismatch"
    UNRESOLVED = "unresolved"


def historical_admits(target_id: str, source_id: str) -> bool:
    """Reduced policy from c83fa7e, given that a search hit already exists."""
    if re.match(r"^Q\d+$", target_id.strip() or ""):
        if source_id and source_id != target_id.strip():
            return False
    return True


def identity_decision(target_id: str | None, source_id: str | None) -> Decision:
    """Proposed policy; both IDs must already use the same canonical namespace."""
    def normalize(value: str | None) -> str | None:
        value = (value or "").strip()
        return value if re.fullmatch(r"Q[1-9][0-9]*", value) else None

    target, source = normalize(target_id), normalize(source_id)
    if target is None or source is None:
        return Decision.UNRESOLVED
    return Decision.MATCH if target == source else Decision.MISMATCH


class IdentityBoundaryTests(unittest.TestCase):
    def test_decision_matrix(self):
        cases = [
            ("same ID", "Q100", "Q100", True, Decision.MATCH),
            ("different ID", "Q100", "Q200", False, Decision.MISMATCH),
            ("missing source", "Q100", "", True, Decision.UNRESOLVED),
            ("missing target", "", "Q200", True, Decision.UNRESOLVED),
            ("noncanonical target", "local-123", "Q200", True, Decision.UNRESOLVED),
            ("both missing", "", "", True, Decision.UNRESOLVED),
        ]
        for name, target, source, historical, proposed in cases:
            with self.subTest(name=name):
                self.assertEqual(historical_admits(target, source), historical)
                self.assertEqual(identity_decision(target, source), proposed)

    def test_same_name_does_not_make_different_ids_match(self):
        # Titles are not inputs to this gate: a namesake still has a different ID.
        self.assertEqual(identity_decision("Q100", "Q200"), Decision.MISMATCH)

    def test_translation_does_not_change_canonical_identity(self):
        for source_language in ("de", "en", "fr"):
            with self.subTest(language=source_language):
                self.assertEqual(identity_decision("Q100", "Q100"), Decision.MATCH)

    def test_missing_metadata_is_not_an_identity_match(self):
        self.assertEqual(identity_decision("Q100", None), Decision.UNRESOLVED)

    def test_whitespace_is_normalized(self):
        self.assertEqual(identity_decision(" Q100 ", "Q100\n"), Decision.MATCH)

    def test_bad_or_foreign_namespace_is_unresolved(self):
        for source in ("Q0", "Q-1", "other:Q100", "q100"):
            with self.subTest(source=source):
                self.assertEqual(identity_decision("Q100", source), Decision.UNRESOLVED)

    def test_syntax_is_not_existence_validation(self):
        # Deliberate limit: two identical invented Q-shaped IDs still compare equal.
        self.assertEqual(identity_decision("Q999999999999", "Q999999999999"), Decision.MATCH)


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