"""Tests for cortex.extraction.tautology_filter."""
from __future__ import annotations

import pytest

from cortex.extraction.tautology_filter import (
    AGENT_ALIASES,
    CONTEXT_TAUTOLOGY_PREDICATES,
    SYNONYM_PREDICATE_GROUPS,
    _is_context_tautology,
    _is_identity_tautology,
    _is_low_information,
    _is_reflexive,
    filter_tautologies,
    get_canonical_predicate,
)


# ===================================================================
# Helpers
# ===================================================================

def _claim(subject: str, predicate: str, object_value: str) -> dict:
    return {"subject": subject, "predicate": predicate, "object_value": object_value}


_DEFAULT_AGENT_ALIASES = AGENT_ALIASES | {"eva"}


# ===================================================================
# Rule 1: Reflexive
# ===================================================================

class TestReflexive:
    """Rule 1 — subject/object refer to the same entity."""

    def test_literal_identity(self):
        assert _is_reflexive("Eva", "Eva", _DEFAULT_AGENT_ALIASES)

    def test_literal_identity_case_insensitive(self):
        assert _is_reflexive("eva", "EVA", _DEFAULT_AGENT_ALIASES)

    def test_same_alias_group_agent(self):
        assert _is_reflexive("agent", "assistant", _DEFAULT_AGENT_ALIASES)

    def test_same_alias_group_user(self):
        assert _is_reflexive("user", "human", _DEFAULT_AGENT_ALIASES)

    def test_subject_word_is_object(self):
        assert _is_reflexive("user", "workspace", _DEFAULT_AGENT_ALIASES) is False
        assert _is_reflexive("user", "user", _DEFAULT_AGENT_ALIASES)

    def test_not_reflexive_different_entities(self):
        assert _is_reflexive("Andrew", "Python", _DEFAULT_AGENT_ALIASES) is False

    def test_not_reflexive_specific_content(self):
        """Real claims should pass through."""
        assert _is_reflexive("Eva", "curious about philosophy", _DEFAULT_AGENT_ALIASES) is False


# ===================================================================
# Rule 2: Context Tautology
# ===================================================================

class TestContextTautology:
    """Rule 2 — always-true-given-system-exists facts."""

    def test_user_interacts_with_eva(self):
        assert _is_context_tautology("user", "interacts_with", "Eva", _DEFAULT_AGENT_ALIASES)

    def test_eva_serves_user(self):
        assert _is_context_tautology("Eva", "serves", "user", _DEFAULT_AGENT_ALIASES)

    def test_user_has_agent_eva(self):
        assert _is_context_tautology("user", "has_agent", "Eva", _DEFAULT_AGENT_ALIASES)

    def test_andrew_uses_eva(self):
        assert _is_context_tautology("Andrew", "uses", "Eva", _DEFAULT_AGENT_ALIASES)

    def test_not_context_specific_tool(self):
        """'user uses VS Code' should NOT be filtered — specific tool preference."""
        assert _is_context_tautology("user", "uses", "VS Code for development", _DEFAULT_AGENT_ALIASES) is False

    def test_not_context_non_system_entities(self):
        """'Andrew interacts_with Kubernetes community' should NOT be filtered."""
        assert _is_context_tautology("Andrew", "interacts_with", "Kubernetes community", _DEFAULT_AGENT_ALIASES) is False

    def test_not_context_non_tautology_predicate(self):
        assert _is_context_tautology("user", "prefers", "Eva", _DEFAULT_AGENT_ALIASES) is False

    def test_all_context_predicates_recognized(self):
        """Verify all predicates in the set trigger for user→agent."""
        for pred in CONTEXT_TAUTOLOGY_PREDICATES:
            assert _is_context_tautology("user", pred, "Eva", _DEFAULT_AGENT_ALIASES), f"{pred} not caught"


# ===================================================================
# Rule 3: Identity Tautology
# ===================================================================

class TestIdentityTautology:
    """Rule 3 — self-evident role assertions."""

    def test_eva_is_agent(self):
        assert _is_identity_tautology("Eva", "is", "agent", _DEFAULT_AGENT_ALIASES)

    def test_eva_is_ai_assistant(self):
        assert _is_identity_tautology("Eva", "is", "AI assistant", _DEFAULT_AGENT_ALIASES)

    def test_user_is_human(self):
        assert _is_identity_tautology("user", "is", "human", _DEFAULT_AGENT_ALIASES)

    def test_eva_is_a_companion(self):
        assert _is_identity_tautology("Eva", "is_a", "companion", _DEFAULT_AGENT_ALIASES)

    def test_not_identity_specific_trait(self):
        """'Eva is curious about philosophy' — real claim, should NOT be filtered."""
        assert _is_identity_tautology("Eva", "is", "curious about philosophy", _DEFAULT_AGENT_ALIASES) is False

    def test_not_identity_wrong_predicate(self):
        assert _is_identity_tautology("Eva", "likes", "agent", _DEFAULT_AGENT_ALIASES) is False


# ===================================================================
# Rule 4: Low Information
# ===================================================================

class TestLowInformation:
    """Rule 4 — empty/trivial object values."""

    def test_empty_object(self):
        assert _is_low_information("user", "has", "", _DEFAULT_AGENT_ALIASES)

    def test_very_short_object(self):
        assert _is_low_information("user", "has", "ok", _DEFAULT_AGENT_ALIASES)

    def test_single_char(self):
        assert _is_low_information("user", "has", "x", _DEFAULT_AGENT_ALIASES)

    def test_system_entity_as_object(self):
        """'Eva knows Eva' — system entity name as sole object, system subject."""
        assert _is_low_information("Eva", "knows", "eva", _DEFAULT_AGENT_ALIASES)

    def test_not_low_info_real_content(self):
        assert _is_low_information("user", "prefers", "Python over JavaScript", _DEFAULT_AGENT_ALIASES) is False

    def test_not_low_info_three_chars(self):
        """Exactly 3 chars — borderline, but passes (>= MIN_OBJECT_VALUE_LENGTH)."""
        assert _is_low_information("user", "has", "cat", _DEFAULT_AGENT_ALIASES) is False


# ===================================================================
# get_canonical_predicate
# ===================================================================

class TestCanonicalPredicate:
    """Synonym mapping for future dedup."""

    def test_synonym_uses(self):
        canonical = get_canonical_predicate("uses")
        assert canonical == get_canonical_predicate("interacts_with")
        assert canonical == get_canonical_predicate("works_with")

    def test_synonym_likes(self):
        canonical = get_canonical_predicate("likes")
        assert canonical == get_canonical_predicate("enjoys")
        assert canonical == get_canonical_predicate("appreciates")

    def test_unknown_predicate_passthrough(self):
        assert get_canonical_predicate("some_random_predicate") == "some_random_predicate"

    def test_case_insensitive(self):
        assert get_canonical_predicate("Uses") == get_canonical_predicate("USES")

    def test_all_groups_have_canonical(self):
        for group in SYNONYM_PREDICATE_GROUPS:
            canonical = sorted(group)[0]
            for pred in group:
                assert get_canonical_predicate(pred) == canonical


# ===================================================================
# filter_tautologies — integration
# ===================================================================

class TestFilterTautologies:
    """Main entry point with mixed real + tautological claims."""

    def test_mixed_claims(self):
        claims = [
            _claim("user", "interacts_with", "Eva"),       # tautology (context)
            _claim("Eva", "is", "agent"),                   # tautology (identity)
            _claim("user", "prefers", "Python over JS"),    # REAL
            _claim("Eva", "is", "Eva"),                     # tautology (reflexive)
            _claim("user", "has", ""),                      # tautology (low-info)
            _claim("Andrew", "lives_in", "Bangkok"),        # REAL
        ]
        result = filter_tautologies(claims, agent_name="eva")
        assert len(result) == 2
        assert result[0]["object_value"] == "Python over JS"
        assert result[1]["object_value"] == "Bangkok"

    def test_all_real_claims_pass(self):
        claims = [
            _claim("Andrew", "lives_in", "Bangkok"),
            _claim("user", "prefers", "dark mode"),
            _claim("Eva", "is", "curious about philosophy"),
            _claim("Andrew", "knows", "Python, Rust, TypeScript"),
            _claim("user", "uses", "VS Code for all development"),
        ]
        result = filter_tautologies(claims, agent_name="eva")
        assert len(result) == 5

    def test_all_tautologies_removed(self):
        claims = [
            _claim("user", "interacts_with", "Eva"),
            _claim("Eva", "serves", "user"),
            _claim("Eva", "is", "agent"),
            _claim("user", "is", "human"),
            _claim("Eva", "is", "Eva"),
            _claim("user", "has", ""),
        ]
        result = filter_tautologies(claims, agent_name="eva")
        assert len(result) == 0

    def test_empty_input(self):
        assert filter_tautologies([], agent_name="eva") == []

    def test_agent_name_dynamic(self):
        """Custom agent name gets added to agent aliases."""
        claims = [
            _claim("user", "interacts_with", "Kira"),  # should be tautology with agent_name="kira"
            _claim("Kira", "is", "agent"),              # identity tautology
            _claim("user", "prefers", "Kira's style"),  # REAL — object is not just a system entity
        ]
        result = filter_tautologies(claims, agent_name="kira")
        assert len(result) == 1
        assert result[0]["object_value"] == "Kira's style"

    def test_none_fields_handled(self):
        """Claims with None fields should not crash."""
        claims = [
            {"subject": None, "predicate": "is", "object_value": "something"},
            {"subject": "user", "predicate": None, "object_value": "something"},
            {"subject": "user", "predicate": "is", "object_value": None},
        ]
        result = filter_tautologies(claims, agent_name="eva")
        # None subject/predicate/object should still work without crashing
        assert isinstance(result, list)

    def test_missing_fields_handled(self):
        """Claims with missing keys should not crash."""
        claims = [
            {"subject": "user"},  # missing predicate and object_value
            {},                   # completely empty dict
        ]
        result = filter_tautologies(claims, agent_name="eva")
        assert isinstance(result, list)

    def test_dataclass_like_objects(self):
        """Should work with objects that have attributes (not just dicts)."""

        class FakeClaim:
            def __init__(self, subject, predicate, object_value):
                self.subject = subject
                self.predicate = predicate
                self.object_value = object_value

        claims = [
            FakeClaim("user", "interacts_with", "Eva"),   # tautology
            FakeClaim("Andrew", "lives_in", "Bangkok"),    # real
        ]
        result = filter_tautologies(claims, agent_name="eva")
        assert len(result) == 1
        assert result[0].object_value == "Bangkok"

    def test_interacts_with_non_system_preserved(self):
        """interacts_with between non-system entities should be kept."""
        claims = [
            _claim("Andrew", "interacts_with", "Kubernetes community"),
        ]
        result = filter_tautologies(claims, agent_name="eva")
        assert len(result) == 1

    def test_uses_specific_tool_preserved(self):
        """'user uses VS Code for development' — specific, not a user↔agent tautology."""
        claims = [
            _claim("user", "uses", "VS Code for all development"),
        ]
        result = filter_tautologies(claims, agent_name="eva")
        assert len(result) == 1

    def test_eva_has_memory_system(self):
        """'Eva has memory system using Cortex' — specific fact, not circular."""
        claims = [
            _claim("Eva", "has", "memory system using Cortex"),
        ]
        result = filter_tautologies(claims, agent_name="eva")
        assert len(result) == 1
