"""Tests for personal knowledge types — mood, habit, goal, reflection, learning, values, relationship.

Verifies:
- All personal entry types are accepted by KnowledgeStore.create()
- Classification level auto-assignment (4 for personal, 3 for technical)
- PERSONAL_ENTRY_TYPES constant consistency with ENTRY_TYPES
- Classification level constants
- Migration 008 creates metadata records
- Search/filter by personal entry types
- Sync exclusion of cognitive-level entries (classification_level >= 4)
"""
import pytest

from greymatter_core.db import SQLiteDatabase
from greymatter_core.knowledge import (
    CLASSIFICATION_COGNITIVE,
    CLASSIFICATION_MAX,
    CLASSIFICATION_MIN,
    CLASSIFICATION_OPEN,
    CLASSIFICATION_PERSONAL,
    CLASSIFICATION_PUBLIC,
    CLASSIFICATION_RESTRICTED,
    CLASSIFICATION_TEAM,
    ENTRY_TYPES,
    KnowledgeStore,
    PERSONAL_ENTRY_TYPES,
)


@pytest.fixture
def db():
    """In-memory SQLite database with full schema."""
    return SQLiteDatabase(":memory:")


@pytest.fixture
def knowledge(db):
    return KnowledgeStore(db)


# ---------------------------------------------------------------------------
# Constants consistency
# ---------------------------------------------------------------------------


class TestConstants:
    """Verify personal knowledge type constants are correct and consistent."""

    def test_personal_types_are_subset_of_entry_types(self):
        """Every personal type must exist in ENTRY_TYPES."""
        assert PERSONAL_ENTRY_TYPES.issubset(ENTRY_TYPES)

    def test_personal_types_complete(self):
        """All seven personal types are present."""
        expected = {"mood", "habit", "goal", "reflection", "learning", "values", "relationship"}
        assert PERSONAL_ENTRY_TYPES == expected

    def test_classification_level_values(self):
        """Each classification level maps to the correct integer."""
        assert CLASSIFICATION_OPEN == 0
        assert CLASSIFICATION_TEAM == 1
        assert CLASSIFICATION_RESTRICTED == 2
        assert CLASSIFICATION_PERSONAL == 3
        assert CLASSIFICATION_COGNITIVE == 4

    def test_classification_level_ordering(self):
        """Classification levels follow 0 < 1 < 2 < 3 < 4."""
        assert CLASSIFICATION_OPEN < CLASSIFICATION_TEAM
        assert CLASSIFICATION_TEAM < CLASSIFICATION_RESTRICTED
        assert CLASSIFICATION_RESTRICTED < CLASSIFICATION_PERSONAL
        assert CLASSIFICATION_PERSONAL < CLASSIFICATION_COGNITIVE

    def test_classification_public_alias(self):
        """CLASSIFICATION_PUBLIC is an alias for CLASSIFICATION_OPEN."""
        assert CLASSIFICATION_PUBLIC == CLASSIFICATION_OPEN

    def test_classification_min_max_bounds(self):
        """MIN and MAX cover the full range of valid levels."""
        assert CLASSIFICATION_MIN == 0
        assert CLASSIFICATION_MAX == 4

    def test_technical_types_not_in_personal(self):
        """Technical types should not overlap with personal types."""
        technical = {"pattern", "correction", "expertise", "gotcha", "reference", "observation"}
        assert technical.isdisjoint(PERSONAL_ENTRY_TYPES)

    def test_technical_types_in_entry_types(self):
        """Technical types should all be in ENTRY_TYPES."""
        technical = {"pattern", "correction", "expertise", "gotcha", "reference", "observation"}
        assert technical.issubset(ENTRY_TYPES)


# ---------------------------------------------------------------------------
# Creation of personal entry types
# ---------------------------------------------------------------------------


class TestPersonalTypeCreation:
    """Each personal entry type should be creatable via KnowledgeStore."""

    @pytest.mark.parametrize("entry_type", sorted(PERSONAL_ENTRY_TYPES))
    def test_create_personal_type(self, knowledge, entry_type):
        """Personal entry type '{entry_type}' should be accepted."""
        entry = knowledge.create(
            entry_type=entry_type,
            title=f"Test {entry_type}",
            content=f"Content for {entry_type}",
        )
        assert entry is not None
        assert entry["id"].startswith("kn-")
        assert entry["entry_type"] == entry_type

    @pytest.mark.parametrize("entry_type", sorted(PERSONAL_ENTRY_TYPES))
    def test_personal_type_auto_classification_level_4(self, knowledge, entry_type):
        """Personal types should auto-assign classification_level=4 (Cognitive)."""
        entry = knowledge.create(
            entry_type=entry_type,
            title=f"Test {entry_type}",
            content=f"Content for {entry_type}",
        )
        assert entry["classification_level"] == CLASSIFICATION_COGNITIVE

    def test_technical_type_default_classification_level_3(self, knowledge):
        """Technical types should default to classification_level=3 (Personal)."""
        entry = knowledge.create(
            entry_type="expertise",
            title="Test expertise",
            content="Content",
        )
        assert entry["classification_level"] == CLASSIFICATION_PERSONAL

    def test_explicit_classification_overrides_default(self, knowledge):
        """Explicit classification_level should override the auto-assignment."""
        entry = knowledge.create(
            entry_type="mood",
            title="Public mood",
            content="Feeling great",
            classification_level=CLASSIFICATION_PUBLIC,
        )
        assert entry["classification_level"] == CLASSIFICATION_PUBLIC

    def test_create_mood_with_valence(self, knowledge):
        """Mood entries can carry valence for sentiment tracking."""
        entry = knowledge.create(
            entry_type="mood",
            title="Morning energy",
            content="High energy after good sleep",
            valence="positive",
        )
        assert entry["valence"] == "positive"
        assert entry["entry_type"] == "mood"

    def test_create_goal_with_domain(self, knowledge):
        """Goals can be domain-scoped."""
        entry = knowledge.create(
            entry_type="goal",
            title="Learn Rust ownership",
            content="Complete the Rust book chapters on lifetimes",
            domain="learning",
        )
        assert entry["domain"] == "learning"
        assert entry["entry_type"] == "goal"

    def test_create_relationship_with_tags(self, knowledge):
        """Relationship entries can carry tags for categorization."""
        entry = knowledge.create(
            entry_type="relationship",
            title="Mentor interactions",
            content="Weekly pairing sessions improve focus",
            tags="mentor,collaboration",
        )
        assert "mentor" in entry["tags"]
        assert entry["entry_type"] == "relationship"

    def test_create_habit_with_scope(self, knowledge):
        """Habit entries support scope assignment."""
        entry = knowledge.create(
            entry_type="habit",
            title="Code review before merge",
            content="Always review my own PR before requesting reviews",
            scope="domain-specific",
        )
        assert entry["scope"] == "domain-specific"

    def test_create_reflection_entry(self, knowledge):
        """Reflection entries are journal-style insights."""
        entry = knowledge.create(
            entry_type="reflection",
            title="Sprint retro insight",
            content="Smaller PRs lead to faster reviews — validated over 3 sprints",
        )
        assert entry["entry_type"] == "reflection"
        assert entry["classification_level"] == CLASSIFICATION_COGNITIVE

    def test_create_values_entry(self, knowledge):
        """Values entries store core beliefs."""
        entry = knowledge.create(
            entry_type="values",
            title="Ship quality over speed",
            content="A week spent on tests saves a month of bugs in production",
        )
        assert entry["entry_type"] == "values"
        assert entry["classification_level"] == CLASSIFICATION_COGNITIVE

    def test_create_learning_entry(self, knowledge):
        """Learning entries track knowledge gaps and progress."""
        entry = knowledge.create(
            entry_type="learning",
            title="SQLite WAL internals",
            content="Need to understand checkpoint mechanism for WAL mode",
        )
        assert entry["entry_type"] == "learning"
        assert entry["classification_level"] == CLASSIFICATION_COGNITIVE


# ---------------------------------------------------------------------------
# Search and filter for personal types
# ---------------------------------------------------------------------------


class TestPersonalTypeSearch:
    """Verify search operations work with personal knowledge types."""

    def test_search_by_personal_type(self, knowledge):
        """Can filter search results by personal entry type."""
        knowledge.create(entry_type="mood", title="Happy", content="Good day")
        knowledge.create(entry_type="expertise", title="Python", content="Expert")
        results = knowledge.search(entry_type="mood")
        assert len(results) == 1
        assert results[0]["entry_type"] == "mood"

    def test_search_text_across_personal_types(self, knowledge):
        """Text search should work across personal entry types."""
        knowledge.create(entry_type="goal", title="Learn Rust", content="Ownership model")
        knowledge.create(entry_type="learning", title="Rust progress", content="Chapter 4 done")
        results = knowledge.search(text="Rust")
        assert len(results) == 2

    def test_search_summary_includes_personal_types(self, knowledge):
        """search_summary should return personal type entries."""
        knowledge.create(entry_type="habit", title="Daily standup", content="Every morning")
        result = knowledge.search_summary(entry_type="habit")
        assert result["total"] == 1
        assert result["entries"][0]["entry_type"] == "habit"

    def test_count_by_personal_type(self, knowledge):
        """count() should work with personal entry types."""
        knowledge.create(entry_type="reflection", title="R1", content="Insight 1")
        knowledge.create(entry_type="reflection", title="R2", content="Insight 2")
        knowledge.create(entry_type="expertise", title="E1", content="Expert 1")
        assert knowledge.count(entry_type="reflection") == 2
        assert knowledge.count(entry_type="expertise") == 1

    def test_prune_personal_entry(self, knowledge):
        """Personal entries can be pruned like any other."""
        entry = knowledge.create(entry_type="mood", title="Bad day", content="Rough")
        knowledge.prune(entry["id"])
        assert knowledge.get(entry["id"])["pruned_at"] is not None
        assert knowledge.count(entry_type="mood") == 0

    def test_update_personal_entry(self, knowledge):
        """Personal entries can be updated."""
        entry = knowledge.create(entry_type="goal", title="Original", content="V1")
        updated = knowledge.update(entry["id"], title="Updated goal")
        assert updated["title"] == "Updated goal"
        assert updated["entry_type"] == "goal"


# ---------------------------------------------------------------------------
# Migration 008 — personal knowledge types metadata
# ---------------------------------------------------------------------------


class TestMigration008:
    """Verify migration 008 writes correct metadata."""

    def test_migration_sets_personal_types_metadata(self, db):
        """Migration 008 should record personal_knowledge_types in metadata."""
        from greymatter_core.schema.migrations import apply_all
        apply_all(db)
        row = db.execute_one(
            "SELECT value FROM _schema_metadata WHERE key = ?",
            ("personal_knowledge_types",),
        )
        assert row is not None
        types = set(row["value"].split(","))
        assert types == {"mood", "habit", "goal", "reflection", "learning", "values", "relationship"}

    def test_migration_sets_personal_domains_metadata(self, db):
        """Migration 008 should record personal_knowledge_domains in metadata."""
        from greymatter_core.schema.migrations import apply_all
        apply_all(db)
        row = db.execute_one(
            "SELECT value FROM _schema_metadata WHERE key = ?",
            ("personal_knowledge_domains",),
        )
        assert row is not None
        domains = set(row["value"].split(","))
        assert domains == {"personal", "learning", "health", "relationships", "career", "creativity"}

    def test_migration_adds_classification_level_column(self, db):
        """Schema should have classification_level column on knowledge_entries."""
        row = db.execute_one(
            "SELECT classification_level FROM knowledge_entries LIMIT 0"
        )
        # No error means the column exists (empty result is fine)

    def test_classification_level_default_in_schema(self, db):
        """DDL default for classification_level should be 3 (Personal)."""
        db.execute(
            "INSERT INTO knowledge_entries (id, entry_type, title, content) VALUES (?, ?, ?, ?)",
            ("kn-test-default", "expertise", "Test", "Content"),
        )
        row = db.execute_one("SELECT classification_level FROM knowledge_entries WHERE id = 'kn-test-default'")
        assert row["classification_level"] == 3


# ---------------------------------------------------------------------------
# Sync exclusion for cognitive-level entries
# ---------------------------------------------------------------------------


class TestSyncExclusion:
    """Cognitive-level entries (classification_level >= 4) must never sync."""

    def test_cognitive_entries_have_level_4(self, knowledge):
        """All personal types should store with classification_level=4."""
        for entry_type in PERSONAL_ENTRY_TYPES:
            entry = knowledge.create(
                entry_type=entry_type,
                title=f"Sync test {entry_type}",
                content=f"Content for {entry_type}",
            )
            assert entry["classification_level"] >= 4, (
                f"{entry_type} should have classification_level >= 4, "
                f"got {entry['classification_level']}"
            )

    def test_technical_entries_below_cognitive_threshold(self, knowledge):
        """Technical entries should have classification_level < 4."""
        entry = knowledge.create(
            entry_type="expertise",
            title="Sync-eligible",
            content="This should sync",
        )
        assert entry["classification_level"] < 4


# ---------------------------------------------------------------------------
# Edge cases
# ---------------------------------------------------------------------------


class TestEdgeCases:
    """Edge cases for personal knowledge type handling."""

    def test_invalid_type_still_rejected(self, knowledge):
        """Types not in ENTRY_TYPES should still raise ValueError."""
        with pytest.raises(ValueError, match="Invalid entry_type"):
            knowledge.create(entry_type="emotion", title="X", content="X")

    def test_all_personal_types_round_trip(self, knowledge):
        """Create, read, update, prune cycle for each personal type."""
        for entry_type in PERSONAL_ENTRY_TYPES:
            entry = knowledge.create(
                entry_type=entry_type,
                title=f"RT {entry_type}",
                content=f"Original {entry_type}",
            )
            fetched = knowledge.get(entry["id"])
            assert fetched["entry_type"] == entry_type
            assert fetched["classification_level"] == CLASSIFICATION_COGNITIVE

            updated = knowledge.update(entry["id"], content=f"Updated {entry_type}")
            assert updated["content"] == f"Updated {entry_type}"

            knowledge.prune(entry["id"])
            assert knowledge.get(entry["id"])["pruned_at"] is not None

    def test_mixed_personal_and_technical_search(self, knowledge):
        """Search without type filter returns both personal and technical."""
        knowledge.create(entry_type="mood", title="Happy", content="Good day")
        knowledge.create(entry_type="expertise", title="Python", content="Expert")
        all_results = knowledge.search()
        assert len(all_results) == 2
        types = {r["entry_type"] for r in all_results}
        assert types == {"mood", "expertise"}

    def test_personal_entry_with_lineage(self, knowledge):
        """create_with_lineage should work for personal types too."""
        entry = knowledge.create_with_lineage(
            entry_type="reflection",
            title="Lineage test",
            content="Promoted from observation",
            source_observation_ids=[],
        )
        assert entry["entry_type"] == "reflection"
        assert entry["classification_level"] == CLASSIFICATION_COGNITIVE

    def test_invalid_classification_level_too_high(self, knowledge):
        """classification_level above MAX raises ValueError."""
        with pytest.raises(
            ValueError, match="Invalid classification_level"
        ):
            knowledge.create(
                entry_type="expertise",
                title="X",
                content="X",
                classification_level=5,
            )

    def test_invalid_classification_level_negative(self, knowledge):
        """Negative classification_level raises ValueError."""
        with pytest.raises(
            ValueError, match="Invalid classification_level"
        ):
            knowledge.create(
                entry_type="expertise",
                title="X",
                content="X",
                classification_level=-1,
            )

    def test_explicit_open_classification(self, knowledge):
        """Entries can be set to OPEN (0) classification."""
        entry = knowledge.create(
            entry_type="expertise",
            title="Public knowledge",
            content="Shareable with anyone",
            classification_level=CLASSIFICATION_OPEN,
        )
        assert entry["classification_level"] == 0

    def test_explicit_restricted_classification(self, knowledge):
        """Entries can be set to RESTRICTED (2) classification."""
        entry = knowledge.create(
            entry_type="expertise",
            title="Restricted knowledge",
            content="Limited distribution",
            classification_level=CLASSIFICATION_RESTRICTED,
        )
        assert entry["classification_level"] == 2

    def test_explicit_team_classification(self, knowledge):
        """Entries can be set to TEAM (1) classification."""
        entry = knowledge.create(
            entry_type="expertise",
            title="Team knowledge",
            content="Shareable within team",
            classification_level=CLASSIFICATION_TEAM,
        )
        assert entry["classification_level"] == 1
