"""Integration tests: cross-module interactions within greymatter_core.

Tests the integration between KnowledgeStore, PatternManager, SessionManager,
WorkItemManager, SoulManager, and QualityEngine sharing a single SQLite DB.

These tests verify that:
- Patterns created from observations maintain referential integrity
- Knowledge entries respect classification level enforcement
- Session lifecycle correctly tracks patterns and work items
- Quality engine operates on knowledge created by other modules
- Confidence escalation across sessions affects pattern visibility
"""

import pytest
from pathlib import Path

from greymatter_core.db import SQLiteDatabase
from greymatter_core.knowledge import (
    KnowledgeStore,
    CLASSIFICATION_COGNITIVE,
    CLASSIFICATION_PERSONAL,
    CLASSIFICATION_OPEN,
    CLASSIFICATION_TEAM,
    PERSONAL_ENTRY_TYPES,
)
from greymatter_core.patterns import PatternManager
from greymatter_core.sessions import SessionManager
from greymatter_core.work_items import WorkItemManager
from greymatter_core.quality import QualityEngine
from greymatter_core.soul import SoulManager


@pytest.fixture
def db():
    """Shared in-memory database for cross-module tests."""
    return SQLiteDatabase(":memory:")


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


@pytest.fixture
def patterns(db):
    return PatternManager(db)


@pytest.fixture
def sessions(db):
    return SessionManager(db)


@pytest.fixture
def work_items(db):
    return WorkItemManager(db)


@pytest.fixture
def quality(db):
    return QualityEngine(db)


@pytest.fixture
def soul(tmp_path):
    return SoulManager(tmp_path / "soul" / "default.md")


# --- Knowledge + Pattern Integration ---


class TestKnowledgePatternIntegration:
    """Tests that knowledge and pattern modules interact correctly on shared DB."""

    def test_pattern_and_knowledge_coexist(self, knowledge, patterns):
        """Both modules create entries in the same DB without conflict."""
        k = knowledge.create(
            entry_type="expertise",
            title="WAL Mode",
            content="SQLite WAL allows concurrent reads during writes",
            domain="database",
        )
        p = patterns.create(
            type="process", domain="database",
            title="Always use WAL", observation="WAL mode improves concurrency",
        )
        assert k["id"].startswith("kn-")
        assert p["id"].startswith("pat-")
        assert knowledge.get(k["id"]) is not None
        assert patterns.get(p["id"]) is not None

    def test_knowledge_from_pattern_domain_consistency(self, knowledge, patterns):
        """Knowledge entries created in the same domain as patterns are searchable together."""
        patterns.create(
            type="process", domain="auth",
            title="JWT Validation", observation="Always validate exp claim",
        )
        knowledge.create(
            entry_type="gotcha", title="JWT Clock Skew",
            content="Allow 30s clock skew in JWT validation",
            domain="auth",
        )
        k_results = knowledge.search(domain="auth")
        p_results = patterns.search(domain="auth")
        assert len(k_results) == 1
        assert len(p_results) == 1
        assert k_results[0]["domain"] == p_results[0]["domain"] == "auth"

    def test_pattern_confidence_escalation_across_sessions(self, patterns, sessions):
        """Pattern confidence escalates as evidence accumulates across sessions."""
        pat = patterns.create(
            type="preference", domain="testing",
            title="TDD First", observation="Write tests before code",
        )
        assert pat["confidence"] == "low"

        for i in range(3):
            session = sessions.start(project="greymatter")
            patterns.add_evidence(pat["id"], session_id=session["id"])
            sessions.end(session["id"], summary=f"Applied TDD in session {i}")

        updated = patterns.get(pat["id"])
        assert updated["confidence"] == "medium"
        assert updated["confirmations"] == 3

    def test_high_confidence_patterns_visible_in_session_load(self, patterns, sessions):
        """Only medium/high confidence patterns appear in session load."""
        low = patterns.create(
            type="process", domain="coding",
            title="Low Confidence", observation="Tentative pattern",
        )
        high = patterns.create(
            type="process", domain="coding",
            title="High Confidence", observation="Well-established pattern",
        )
        for i in range(8):
            session = sessions.start(project="greymatter")
            patterns.add_evidence(high["id"], session_id=session["id"])
            sessions.end(session["id"])

        session_patterns = patterns.get_for_session_load()
        loaded_ids = [p["id"] for p in session_patterns]
        assert high["id"] in loaded_ids
        assert low["id"] not in loaded_ids


# --- Classification Level Enforcement ---


class TestClassificationEnforcement:
    """Verify personal entry types auto-set to Cognitive and enforce boundaries."""

    def test_personal_types_auto_cognitive(self, knowledge):
        """Personal entry types (mood, habit, etc.) default to classification_level=4."""
        for entry_type in ("mood", "habit", "goal", "reflection"):
            entry = knowledge.create(
                entry_type=entry_type,
                title=f"Test {entry_type}",
                content=f"Content for {entry_type}",
            )
            assert entry["classification_level"] == CLASSIFICATION_COGNITIVE, (
                f"{entry_type} should default to Cognitive (4), got {entry['classification_level']}"
            )

    def test_technical_types_default_personal(self, knowledge):
        """Technical entry types default to classification_level=3 (Personal)."""
        for entry_type in ("pattern", "correction", "expertise", "gotcha"):
            entry = knowledge.create(
                entry_type=entry_type,
                title=f"Test {entry_type}",
                content=f"Content for {entry_type}",
            )
            assert entry["classification_level"] == CLASSIFICATION_PERSONAL, (
                f"{entry_type} should default to Personal (3), got {entry['classification_level']}"
            )

    def test_explicit_classification_override(self, knowledge):
        """Explicit classification_level overrides the default."""
        entry = knowledge.create(
            entry_type="expertise",
            title="Public Docs",
            content="This is shareable",
            classification_level=CLASSIFICATION_OPEN,
        )
        assert entry["classification_level"] == CLASSIFICATION_OPEN

    def test_invalid_classification_raises(self, knowledge):
        """Out-of-range classification_level raises ValueError."""
        with pytest.raises(ValueError, match="Invalid classification_level"):
            knowledge.create(
                entry_type="expertise",
                title="Bad Level",
                content="This should fail",
                classification_level=99,
            )

    def test_cognitive_entries_excluded_from_open_search(self, knowledge):
        """Cognitive entries should be distinguishable from open entries by level."""
        knowledge.create(
            entry_type="mood",
            title="Feeling productive",
            content="High energy today",
        )
        knowledge.create(
            entry_type="expertise",
            title="API Design",
            content="Use REST conventions",
            classification_level=CLASSIFICATION_OPEN,
        )
        all_entries = knowledge.search()
        assert len(all_entries) == 2
        levels = {e["title"]: e["classification_level"] for e in all_entries}
        assert levels["Feeling productive"] == CLASSIFICATION_COGNITIVE
        assert levels["API Design"] == CLASSIFICATION_OPEN


# --- Session + Work Item Integration ---


class TestSessionWorkItemIntegration:
    """Tests session lifecycle interacting with work items and patterns."""

    def test_session_tracks_project(self, sessions, work_items):
        """Session started with a project matches work item project."""
        work = work_items.create(title="Fix auth bug", project="salesos")
        session = sessions.start(project="salesos")
        assert session["project"] == work["project"]

    def test_session_lifecycle_with_pattern_evidence(self, sessions, patterns):
        """Full session lifecycle: start -> apply patterns -> end with summary."""
        session = sessions.start(project="greymatter")
        assert session["ended_at"] is None

        pat = patterns.create(
            type="process", domain="testing",
            title="Integration tests", observation="Always test cross-module",
        )
        patterns.add_evidence(pat["id"], session_id=session["id"])

        ended = sessions.end(session["id"], summary="Added integration tests")
        assert ended["ended_at"] is not None
        assert ended["summary"] == "Added integration tests"

        pat_updated = patterns.get(pat["id"])
        assert pat_updated["applications"] == 1

    def test_concurrent_sessions(self, sessions):
        """Multiple sessions can exist; ending one leaves the other as current."""
        s1 = sessions.start(project="project-a")
        s2 = sessions.start(project="project-b")

        # Both sessions are active (unended)
        current = sessions.get_current()
        assert current is not None
        assert current["ended_at"] is None

        # End the current session
        sessions.end(current["id"])

        # The other session should now be current
        remaining = sessions.get_current()
        assert remaining is not None
        assert remaining["id"] != current["id"]
        assert remaining["ended_at"] is None

    def test_work_item_status_transitions(self, work_items):
        """Work items follow the expected lifecycle: queued -> in_progress -> completed."""
        work = work_items.create(title="Build feature X")
        assert work["status"] == "queued"

        work_items.update_status(work["id"], "in_progress")
        updated = work_items.get(work["id"])
        assert updated["status"] == "in_progress"

        work_items.update_status(work["id"], "completed")
        completed = work_items.get(work["id"])
        assert completed["status"] == "completed"

    def test_pattern_evidence_links_to_work_item(self, patterns, sessions, work_items):
        """Pattern evidence can reference both session and work item."""
        session = sessions.start(project="greymatter")
        work = work_items.create(title="Implement auth")
        pat = patterns.create(
            type="preference", domain="security",
            title="Use bcrypt", observation="Always hash with bcrypt",
        )
        patterns.add_evidence(
            pat["id"],
            session_id=session["id"],
            work_item_id=work["id"],
        )
        updated = patterns.get(pat["id"])
        assert updated["applications"] == 1
        assert updated["last_applied_at"] is not None


# --- Knowledge Quality Integration ---


class TestKnowledgeQualityIntegration:
    """Tests QualityEngine operating on entries from KnowledgeStore."""

    def test_quality_detects_near_duplicates(self, knowledge, quality):
        """QualityEngine finds near-duplicate titles in the knowledge graph."""
        knowledge.create(
            entry_type="expertise",
            title="SQLite WAL Mode Benefits",
            content="WAL mode allows concurrent reads during writes",
        )
        knowledge.create(
            entry_type="expertise",
            title="SQLite WAL Mode Advantages",
            content="WAL mode enables read concurrency with writes",
        )
        report = quality.full_check()
        assert report.total_entries == 2

    def test_quality_check_on_pruned_entries(self, knowledge, quality):
        """Quality engine counts only active entries after pruning."""
        e1 = knowledge.create(
            entry_type="gotcha", title="Old Bug", content="Fixed long ago",
        )
        knowledge.create(
            entry_type="expertise", title="Current", content="Still relevant",
        )
        knowledge.prune(e1["id"])
        assert knowledge.count() == 1

    def test_knowledge_update_and_quality_recheck(self, knowledge, quality):
        """Updated knowledge entries are reflected in quality checks."""
        entry = knowledge.create(
            entry_type="expertise",
            title="Old Title",
            content="Old content about auth",
        )
        knowledge.update(entry["id"], title="Updated: JWT Auth Best Practices")
        updated = knowledge.get(entry["id"])
        assert updated["title"] == "Updated: JWT Auth Best Practices"
        assert updated["updated_at"] is not None

        report = quality.full_check()
        assert report.total_entries == 1


# --- Soul + Session Integration ---


class TestSoulSessionIntegration:
    """Tests soul document management across sessions."""

    def test_soul_creation_and_load(self, soul):
        """Soul document can be created and loaded."""
        assert not soul.exists()
        soul.create_initial_soul("Keith")
        assert soul.exists()
        content = soul.load()
        assert "Keith" in content
        assert "## Identity" in content

    def test_soul_learned_preferences_accumulate(self, soul):
        """Learned preferences accumulate across multiple additions."""
        soul.create_initial_soul("Keith")
        soul.add_learned_preference("Prefers TDD workflow")
        soul.add_learned_preference("Uses pytest over unittest")

        content = soul.load()
        assert "Prefers TDD workflow" in content
        assert "Uses pytest over unittest" in content

    def test_soul_session_count_increments(self, soul):
        """Session count increments correctly."""
        soul.create_initial_soul("Keith")
        count1 = soul.increment_session_count()
        count2 = soul.increment_session_count()
        assert count2 == count1 + 1

    def test_soul_section_extraction(self, soul):
        """get_section extracts specific sections from soul document."""
        soul.create_initial_soul("Keith")
        identity = soul.get_section("Identity")
        assert identity is not None
        assert "Keith" in identity

        values = soul.get_section("Values")
        assert values is not None
        assert "quality" in values.lower()

    def test_soul_summary_respects_line_limit(self, soul):
        """get_summary truncates output to max_lines."""
        soul.create_initial_soul("Keith")
        for i in range(20):
            soul.add_learned_preference(f"Preference #{i}: Some detailed text here")

        summary = soul.get_summary(max_lines=10)
        assert len(summary.splitlines()) <= 10


# --- Full Pipeline Integration ---


class TestFullPipelineIntegration:
    """End-to-end flows exercising multiple modules in realistic sequences."""

    def test_observation_to_pattern_to_knowledge_pipeline(self, db, knowledge, patterns, sessions):
        """Simulate the full knowledge lifecycle:
        1. Create observations (knowledge entries)
        2. Promote observations to a pattern
        3. Pattern gains evidence across sessions
        4. Knowledge entry created from mature pattern
        """
        obs1 = knowledge.create(
            entry_type="observation",
            title="Agents prefer structured output",
            content="Agents consistently produce better results with JSON schemas",
            domain="orchestration",
        )
        obs2 = knowledge.create(
            entry_type="observation",
            title="Schema validation catches errors early",
            content="Pydantic models prevent malformed agent output",
            domain="orchestration",
        )

        pattern = patterns.create(
            type="process",
            domain="orchestration",
            title="Structured Output Standard",
            observation="All agent interfaces should use Pydantic schemas",
        )

        for i in range(3):
            session = sessions.start(project="greymatter")
            patterns.add_evidence(pattern["id"], session_id=session["id"])
            sessions.end(session["id"], summary=f"Confirmed structured output (session {i})")

        confirmed = patterns.get(pattern["id"])
        assert confirmed["confidence"] == "medium"
        assert confirmed["confirmations"] == 3

        expertise = knowledge.create(
            entry_type="expertise",
            title="Agent Output Schema Standard",
            content="All agent interfaces must use Pydantic v2 models for I/O validation",
            domain="orchestration",
            tags="agents,pydantic,schemas",
        )

        domain_knowledge = knowledge.search(domain="orchestration")
        assert len(domain_knowledge) >= 3
        domain_patterns = patterns.search(domain="orchestration")
        assert len(domain_patterns) == 1

    def test_multi_project_session_isolation(self, sessions, work_items, patterns):
        """Sessions for different projects track independent work and patterns."""
        session_a = sessions.start(project="salesos")
        work_a = work_items.create(title="CRM Integration", project="salesos")
        pat_a = patterns.create(
            type="process", domain="crm",
            title="Enrich before persist", observation="Always enrich contacts before saving",
        )
        patterns.add_evidence(pat_a["id"], session_id=session_a["id"], work_item_id=work_a["id"])
        sessions.end(session_a["id"], summary="CRM work")

        session_b = sessions.start(project="greymatter")
        work_b = work_items.create(title="Fix coordinator", project="greymatter")
        pat_b = patterns.create(
            type="process", domain="orchestration",
            title="Test state machines", observation="Every state transition needs a test",
        )
        patterns.add_evidence(pat_b["id"], session_id=session_b["id"], work_item_id=work_b["id"])
        sessions.end(session_b["id"], summary="Coordinator fix")

        recent = sessions.get_recent(limit=2)
        assert len(recent) == 2
        projects = {s["project"] for s in recent}
        assert projects == {"salesos", "greymatter"}

        crm_patterns = patterns.search(domain="crm")
        orch_patterns = patterns.search(domain="orchestration")
        assert len(crm_patterns) == 1
        assert len(orch_patterns) == 1

    def test_work_item_priority_ordering_with_domain_tags(self, work_items):
        """Work items respect priority ordering and domain tag filtering."""
        work_items.create(title="Nice to have", priority=100, domain_tags="ui")
        work_items.create(title="Critical bug", priority=1, domain_tags="security")
        work_items.create(title="Improvement", priority=50, domain_tags="performance")

        all_items = work_items.list()
        assert len(all_items) == 3
        assert all_items[0]["priority"] <= all_items[1]["priority"] <= all_items[2]["priority"]
        assert all_items[0]["title"] == "Critical bug"

    def test_knowledge_prune_and_search_exclusion(self, knowledge):
        """Pruned knowledge entries are excluded from default search."""
        active = knowledge.create(
            entry_type="expertise", title="Active Entry",
            content="This is current knowledge",
        )
        stale = knowledge.create(
            entry_type="gotcha", title="Stale Gotcha",
            content="This is outdated",
        )
        knowledge.prune(stale["id"])

        results = knowledge.search()
        assert len(results) == 1
        assert results[0]["id"] == active["id"]

        all_results = knowledge.search(include_pruned=True)
        assert len(all_results) == 2
