"""
tests/test_8_category_taxonomy.py

Tests for the 8-category taxonomy (Issue #1455).

Covers:
- VALID_CATEGORIES includes all 8 + misc
- CATEGORY_ALIASES map correctly (including engineering → decisions)
- CATEGORY_TO_INTRINSIC_MAP covers all 8 categories
- ClaimExtraction validates new categories
- New categories pass through validation unchanged
- Old "engineering" alias resolves to "decisions"
- Migration script heuristics classify correctly
"""

import logging

import pytest

from cortex.types.constants import (
    VALID_CATEGORIES,
    CATEGORY_ALIASES,
    CATEGORY_TO_INTRINSIC_MAP,
)
from cortex.extraction.models import (
    VALID_CATEGORY,
    ClaimExtraction,
)


# ---------------------------------------------------------------------------
# Constants tests
# ---------------------------------------------------------------------------

class TestValidCategories:
    """Verify the canonical category set."""

    def test_contains_all_8_categories(self):
        expected = {
            "identity", "personal", "preferences", "decisions",
            "goals", "behavioral", "relational", "episodic",
        }
        assert expected.issubset(VALID_CATEGORIES)

    def test_misc_still_present(self):
        """misc is the fallback — must remain."""
        assert "misc" in VALID_CATEGORIES

    def test_engineering_removed(self):
        """engineering is no longer a valid category."""
        assert "engineering" not in VALID_CATEGORIES

    def test_total_count(self):
        """8 named categories + misc = 9 total."""
        assert len(VALID_CATEGORIES) == 9


class TestCategoryAliases:
    """Verify aliases map to correct canonical categories."""

    def test_engineering_alias_to_decisions(self):
        assert CATEGORY_ALIASES["engineering"] == "decisions"

    def test_technical_alias_to_decisions(self):
        assert CATEGORY_ALIASES["technical"] == "decisions"

    def test_architecture_alias_to_decisions(self):
        assert CATEGORY_ALIASES["architecture"] == "decisions"

    def test_behavior_alias_to_behavioral(self):
        assert CATEGORY_ALIASES["behavior"] == "behavioral"

    def test_habit_alias_to_behavioral(self):
        assert CATEGORY_ALIASES["habit"] == "behavioral"

    def test_relationship_alias_to_relational(self):
        assert CATEGORY_ALIASES["relationship"] == "relational"

    def test_event_alias_to_episodic(self):
        assert CATEGORY_ALIASES["event"] == "episodic"

    def test_milestone_alias_to_episodic(self):
        assert CATEGORY_ALIASES["milestone"] == "episodic"

    def test_tech_alias_to_preferences(self):
        """'tech' (tool choice, no reasoning) → preferences."""
        assert CATEGORY_ALIASES["tech"] == "preferences"

    def test_all_alias_values_are_valid(self):
        """Every alias must resolve to a valid category."""
        for alias, target in CATEGORY_ALIASES.items():
            assert target in VALID_CATEGORIES, (
                f"Alias {alias!r} → {target!r} but {target!r} not in VALID_CATEGORIES"
            )


class TestCategoryToIntrinsicMap:
    """Verify intrinsic type mapping covers all categories."""

    def test_all_categories_mapped(self):
        for cat in VALID_CATEGORIES:
            assert cat in CATEGORY_TO_INTRINSIC_MAP, (
                f"Category {cat!r} has no intrinsic type mapping"
            )

    def test_decisions_maps_to_fact(self):
        assert CATEGORY_TO_INTRINSIC_MAP["decisions"] == "fact"

    def test_behavioral_maps_to_fact(self):
        assert CATEGORY_TO_INTRINSIC_MAP["behavioral"] == "fact"

    def test_relational_maps_to_fact(self):
        assert CATEGORY_TO_INTRINSIC_MAP["relational"] == "fact"

    def test_episodic_maps_to_fact(self):
        assert CATEGORY_TO_INTRINSIC_MAP["episodic"] == "fact"

    def test_goals_maps_to_goal(self):
        assert CATEGORY_TO_INTRINSIC_MAP["goals"] == "goal"

    def test_preferences_maps_to_preference(self):
        assert CATEGORY_TO_INTRINSIC_MAP["preferences"] == "preference"


# ---------------------------------------------------------------------------
# ClaimExtraction model validation
# ---------------------------------------------------------------------------

def _make(**overrides):
    defaults = dict(
        action="ADD",
        subject="user",
        predicate="has",
        object_value="something",
        confidence=0.9,
    )
    defaults.update(overrides)
    return ClaimExtraction(**defaults)


class TestClaimExtractionCategories:
    """Verify ClaimExtraction model accepts new categories."""

    @pytest.mark.parametrize("category", [
        "identity", "personal", "preferences", "decisions",
        "goals", "behavioral", "relational", "episodic", "misc",
    ])
    def test_valid_category_passes_through(self, category):
        c = _make(category=category)
        assert c.category == category

    def test_engineering_is_now_invalid(self, caplog):
        """engineering is no longer in VALID_CATEGORY, should clamp to misc."""
        with caplog.at_level(logging.WARNING, logger="cortex.extraction.models"):
            c = _make(category="engineering")
        assert c.category == "misc"

    def test_unknown_category_clamps_to_misc(self, caplog):
        with caplog.at_level(logging.WARNING, logger="cortex.extraction.models"):
            c = _make(category="bogus_category")
        assert c.category == "misc"

    def test_model_valid_category_set_matches_constants(self):
        """VALID_CATEGORY in models.py must match VALID_CATEGORIES in constants."""
        assert VALID_CATEGORY == VALID_CATEGORIES


# ---------------------------------------------------------------------------
# Migration script heuristics
# ---------------------------------------------------------------------------

class TestMigrationHeuristics:
    """Test the reclassification logic from the migration script."""

    @pytest.fixture(autouse=True)
    def _import_migration(self):
        import importlib.util
        spec = importlib.util.spec_from_file_location(
            "migrate_categories",
            "scripts/migrate_categories.py",
        )
        self.mod = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(self.mod)

    # engineering → decisions / preferences
    def test_eng_chose_goes_to_decisions(self):
        result = self.mod.classify_engineering("chose", "SQLite over Postgres")
        assert result == "decisions"

    def test_eng_decided_goes_to_decisions(self):
        result = self.mod.classify_engineering("decided", "to use Fly.io")
        assert result == "decisions"

    def test_eng_prefers_goes_to_preferences(self):
        result = self.mod.classify_engineering("prefers", "Python for backend")
        assert result == "preferences"

    def test_eng_uses_goes_to_preferences(self):
        result = self.mod.classify_engineering("uses", "Redis for caching")
        assert result == "preferences"

    def test_eng_default_goes_to_decisions(self):
        result = self.mod.classify_engineering("implemented", "rate limiting")
        assert result == "decisions"

    # misc → various categories
    def test_misc_attended_goes_to_episodic(self):
        result = self.mod.classify_misc("attended", "KubeCon 2025", "user")
        assert result == "episodic"

    def test_misc_event_pattern_goes_to_episodic(self):
        result = self.mod.classify_misc("experienced", "outage on March 15", "user")
        assert result == "episodic"

    def test_misc_always_pattern_goes_to_behavioral(self):
        result = self.mod.classify_misc("has", "always reviews PRs first", "user")
        assert result == "behavioral"

    def test_misc_routine_goes_to_behavioral(self):
        result = self.mod.classify_misc("does", "morning routine at 6am every day", "user")
        assert result == "behavioral"

    def test_misc_mentors_goes_to_relational(self):
        result = self.mod.classify_misc("mentors", "Alex on architecture", "Andrew")
        assert result == "relational"

    def test_misc_relationship_pattern_goes_to_relational(self):
        result = self.mod.classify_misc("has", "mentor relationship with X", "user")
        assert result == "relational"

    def test_misc_prefers_goes_to_preferences(self):
        result = self.mod.classify_misc("prefers", "dark mode", "user")
        assert result == "preferences"

    def test_misc_generic_defaults_to_personal(self):
        result = self.mod.classify_misc("is", "34 years old", "user")
        assert result == "personal"
