"""Integration tests: core write operations across modules.

Tests the write-path integration between KnowledgeStore, DocumentStore,
SessionManager, PatternManager, ContextEngine, and QualityEngine — focusing
on areas not covered by existing cross-module and end-to-end integration tests:

- Knowledge staleness lifecycle (flag → confirm → unflag → auto-archive → health)
- Document token budgeting and context retrieval with overflow
- Session state management with multiple checkpoints and recovery
- Knowledge search_summary and record_application interactions
- Document import/export markdown rendering round-trips
- Knowledge health report integrating staleness + duplicates + contradictions
"""

import json
import sqlite3
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any

import pytest

from greymatter_core.db import SQLiteDatabase
from greymatter_core.context import ContextEngine
from greymatter_core.documents import DocumentStore
from greymatter_core.knowledge import (
    CLASSIFICATION_COGNITIVE,
    CLASSIFICATION_OPEN,
    CLASSIFICATION_PERSONAL,
    KnowledgeStore,
)
from greymatter_core.patterns import PatternManager
from greymatter_core.quality import QualityEngine
from greymatter_core.sessions import SessionManager
from greymatter_core.soul import SoulManager
from greymatter_core.standards import StandardsManager
from greymatter_core.work_items import WorkItemManager


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture
def db():
    """Shared in-memory database for integration tests."""
    database = SQLiteDatabase(":memory:")
    yield database
    database.close()


@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 standards(db):
    return StandardsManager(db)


@pytest.fixture
def doc_store(db):
    return DocumentStore(db)


@pytest.fixture
def context_engine(db, doc_store, standards, patterns):
    return ContextEngine(db, doc_store, standards, patterns)


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


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _backdate_entry(db, entry_id, days):
    """Backdate a knowledge entry's timestamps to simulate aging."""
    old_ts = (datetime.now(timezone.utc) - timedelta(days=days)).isoformat()
    db.execute(
        """UPDATE knowledge_entries
           SET created_at = ?, updated_at = ?, last_confirmed_at = NULL,
               stale_flagged_at = NULL
           WHERE id = ?""",
        (old_ts, old_ts, entry_id),
    )


# ---------------------------------------------------------------------------
# 1. Knowledge Staleness Lifecycle
# ---------------------------------------------------------------------------

class TestKnowledgeStalenessLifecycle:
    """Tests the full staleness lifecycle: create -> age -> flag -> confirm/unflag -> archive."""

    def test_fresh_entries_not_flagged_stale(self, knowledge):
        """Recently created entries should not be flagged as stale."""
        knowledge.create(
            entry_type="expertise",
            title="Fresh knowledge",
            content="Just created, should not be stale",
            domain="testing",
        )
        stale = knowledge.flag_stale(stale_days=90)
        assert len(stale) == 0

    def test_old_entries_flagged_stale(self, db, knowledge):
        """Entries older than stale_days without activity are flagged."""
        entry = knowledge.create(
            entry_type="gotcha",
            title="Ancient gotcha",
            content="This was discovered long ago",
            domain="database",
        )
        _backdate_entry(db, entry["id"], days=120)

        stale = knowledge.flag_stale(stale_days=90)
        assert len(stale) == 1
        assert stale[0]["id"] == entry["id"]
        assert stale[0]["stale_flagged_at"] is not None

    def test_confirm_resets_staleness(self, db, knowledge):
        """Confirming an entry clears stale flag and prevents re-flagging."""
        entry = knowledge.create(
            entry_type="expertise",
            title="Still relevant knowledge",
            content="This knowledge is still accurate",
            domain="security",
        )
        _backdate_entry(db, entry["id"], days=120)

        knowledge.flag_stale(stale_days=90)
        stale_before = knowledge.get_stale()
        assert len(stale_before) == 1

        result = knowledge.confirm(entry["id"])
        assert result is True

        stale_after = knowledge.get_stale()
        assert len(stale_after) == 0

        refreshed = knowledge.get(entry["id"])
        assert refreshed["stale_flagged_at"] is None
        assert refreshed["last_confirmed_at"] is not None

    def test_unflag_stale_manual_override(self, db, knowledge):
        """unflag_stale removes the stale flag without confirming."""
        entry = knowledge.create(
            entry_type="reference",
            title="Useful reference",
            content="API endpoint documentation",
            domain="api",
        )
        _backdate_entry(db, entry["id"], days=120)
        knowledge.flag_stale(stale_days=90)

        assert len(knowledge.get_stale()) == 1
        result = knowledge.unflag_stale(entry["id"])
        assert result is True
        assert len(knowledge.get_stale()) == 0

    def test_unflag_nonexistent_entry_returns_false(self, knowledge):
        """unflag_stale returns False for non-existent entry."""
        assert knowledge.unflag_stale("kn-nonexistent") is False

    def test_record_application_clears_stale_flag(self, db, knowledge):
        """record_application increments applied_count and clears stale flag."""
        entry = knowledge.create(
            entry_type="expertise",
            title="Applied knowledge",
            content="Used by an agent in context",
            domain="orchestration",
        )
        _backdate_entry(db, entry["id"], days=120)
        knowledge.flag_stale(stale_days=90)

        assert len(knowledge.get_stale()) == 1

        knowledge.record_application(entry["id"])

        updated = knowledge.get(entry["id"])
        assert updated["applied_count"] == 1
        assert updated["stale_flagged_at"] is None

        assert len(knowledge.get_stale()) == 0

    def test_auto_archive_stale_entries(self, db, knowledge):
        """auto_archive_stale prunes old low-confidence entries."""
        knowledge.create(
            entry_type="expertise",
            title="Active expertise",
            content="Still relevant, high confidence",
            domain="testing",
        )
        archivable = knowledge.create(
            entry_type="gotcha",
            title="Old low-confidence gotcha",
            content="Probably not relevant anymore",
            domain="legacy",
        )
        _backdate_entry(db, archivable["id"], days=200)

        archived = knowledge.auto_archive_stale(
            archive_days=180, confidence_threshold=0.5,
        )
        assert len(archived) >= 1
        archived_ids = [a["id"] for a in archived]
        assert archivable["id"] in archived_ids

        pruned = knowledge.get(archivable["id"])
        assert pruned["pruned_at"] is not None

        assert knowledge.count() == 1

    def test_pruning_summary_reflects_state(self, db, knowledge):
        """pruning_summary returns accurate counts for all categories."""
        e1 = knowledge.create(
            entry_type="expertise", title="Active 1",
            content="Still active", domain="testing",
        )
        e2 = knowledge.create(
            entry_type="gotcha", title="Stale 1",
            content="Will be flagged stale", domain="database",
        )
        e3 = knowledge.create(
            entry_type="reference", title="Pruned 1",
            content="Will be pruned", domain="api",
        )
        _backdate_entry(db, e2["id"], days=120)
        knowledge.flag_stale(stale_days=90)
        knowledge.prune(e3["id"])

        knowledge.record_application(e1["id"])

        summary = knowledge.pruning_summary()
        assert summary["total_active"] == 2
        assert summary["stale_flagged"] == 1
        assert summary["archived"] == 1
        assert summary["never_applied"] >= 1

    def test_staleness_idempotent_flagging(self, db, knowledge):
        """Running flag_stale twice does not double-flag entries."""
        entry = knowledge.create(
            entry_type="expertise", title="Old entry",
            content="Already flagged", domain="testing",
        )
        _backdate_entry(db, entry["id"], days=120)

        first_run = knowledge.flag_stale(stale_days=90)
        assert len(first_run) == 1
        first_flag_time = first_run[0]["stale_flagged_at"]

        second_run = knowledge.flag_stale(stale_days=90)
        for entry_row in second_run:
            assert entry_row["stale_flagged_at"] == first_flag_time


# ---------------------------------------------------------------------------
# 2. Knowledge Health Report Integration
# ---------------------------------------------------------------------------

class TestKnowledgeHealthIntegration:
    """Tests the unified health() report combining staleness, duplicates, contradictions."""

    def test_health_report_clean_graph(self, knowledge):
        """Health report on clean graph shows zero issues."""
        knowledge.create(
            entry_type="expertise", title="SQLite WAL concurrency model",
            content="Content about database concurrency", domain="testing",
        )
        knowledge.create(
            entry_type="gotcha", title="JWT token expiration handling",
            content="Content about authentication tokens", domain="database",
        )

        report = knowledge.health(stale_days=90)
        assert report["summary"]["total_active"] == 2
        assert report["summary"]["stale_flagged"] == 0
        assert report["summary"]["near_duplicate_pairs"] == 0
        assert report["summary"]["contradiction_pairs"] == 0
        assert len(report["stale_entries"]) == 0
        assert len(report["near_duplicates"]) == 0
        assert len(report["contradictions"]) == 0

    def test_health_report_detects_all_issues(self, db, knowledge):
        """Health report surfaces staleness, duplicates, and contradictions."""
        stale_entry = knowledge.create(
            entry_type="expertise", title="Old expertise",
            content="This is outdated", domain="legacy",
        )
        _backdate_entry(db, stale_entry["id"], days=120)

        knowledge.create(
            entry_type="expertise", title="SQLite WAL Benefits",
            content="WAL mode allows concurrent reads",
            domain="database",
        )
        knowledge.create(
            entry_type="expertise", title="SQLite WAL Advantages",
            content="WAL mode enables read concurrency",
            domain="database",
        )

        knowledge.create(
            entry_type="expertise", title="Always use mocks in tests",
            content="Always use mocks for unit testing isolation",
            domain="testing",
        )
        knowledge.create(
            entry_type="correction", title="Never use mocks in tests",
            content="Never use mocks — they hide real bugs",
            domain="testing",
        )

        report = knowledge.health(stale_days=90)
        assert report["summary"]["stale_flagged"] >= 1
        assert report["summary"]["near_duplicate_pairs"] >= 1
        assert report["summary"]["contradiction_pairs"] >= 1
        assert len(report["stale_entries"]) >= 1
        assert len(report["near_duplicates"]) >= 1
        assert len(report["contradictions"]) >= 1

    def test_health_after_remediation(self, db, knowledge):
        """Health report improves after confirming stale and pruning duplicates."""
        stale = knowledge.create(
            entry_type="expertise", title="Stale but valid",
            content="Still relevant", domain="testing",
        )
        _backdate_entry(db, stale["id"], days=120)

        knowledge.create(
            entry_type="expertise", title="Duplicate Entry Topic",
            content="First version", domain="api",
        )
        dup2 = knowledge.create(
            entry_type="expertise", title="Duplicate Entry Topic",
            content="Second version", domain="api",
        )

        report_before = knowledge.health(stale_days=90)
        assert report_before["summary"]["stale_flagged"] >= 1
        assert report_before["summary"]["near_duplicate_pairs"] >= 1

        knowledge.confirm(stale["id"])
        knowledge.prune(dup2["id"])

        report_after = knowledge.health(stale_days=90)
        assert report_after["summary"]["stale_flagged"] == 0
        assert report_after["summary"]["near_duplicate_pairs"] == 0


# ---------------------------------------------------------------------------
# 3. Document Token Budgeting and Context Retrieval
# ---------------------------------------------------------------------------

class TestDocumentTokenBudgetIntegration:
    """Tests document get_context with token budgets and summary fallback."""

    def test_small_docs_fit_within_budget(self, doc_store):
        """Small documents are served in full when within budget."""
        doc_store.create(
            doc_type="design", title="Small Doc",
            content="Short content here.",
            project="greymatter",
        )
        result = doc_store.get_context(project="greymatter", max_tokens=5000)
        assert len(result) == 1
        assert result[0]["content"] == "Short content here."

    def test_large_doc_uses_summary_fallback(self, doc_store):
        """Large documents fall back to summary when exceeding budget."""
        large_content = "x" * 20000  # ~5000 tokens
        doc = doc_store.create(
            doc_type="research", title="Large Research",
            content=large_content,
            project="greymatter",
        )
        doc_store.summarize(doc["id"], "A concise summary of the research.")

        result = doc_store.get_context(project="greymatter", max_tokens=100)
        assert len(result) == 1
        assert result[0]["content"] == "A concise summary of the research."
        assert result[0]["token_estimate"] < 100

    def test_budget_overflow_skips_docs(self, doc_store):
        """Documents that exceed remaining budget (with no summary) are skipped."""
        doc_store.create(
            doc_type="design", title="Small Doc",
            content="Small content.",
            project="greymatter",
        )
        doc_store.create(
            doc_type="research", title="Huge Doc",
            content="y" * 40000,
            project="greymatter",
        )

        result = doc_store.get_context(project="greymatter", max_tokens=100)
        titles = [d["title"] for d in result]
        assert "Small Doc" in titles
        assert "Huge Doc" not in titles

    def test_mixed_budget_allocation(self, doc_store):
        """Multiple docs fill budget progressively; later ones get summaries."""
        doc_store.create(
            doc_type="design", title="Doc 1",
            content="a" * 400,  # ~100 tokens
            project="greymatter",
        )
        doc2 = doc_store.create(
            doc_type="plan", title="Doc 2",
            content="b" * 2000,  # ~500 tokens
            project="greymatter",
        )
        doc_store.summarize(doc2["id"], "Summary of doc 2.")

        result = doc_store.get_context(project="greymatter", max_tokens=200)
        assert len(result) >= 1
        for doc in result:
            assert doc["token_estimate"] <= 200

    def test_context_engine_integrates_doc_budget(
        self, standards, doc_store, context_engine, patterns, sessions,
    ):
        """Context engine respects token budget across standards + docs + patterns."""
        standards.seed_defaults()

        doc_store.create(
            doc_type="design", title="Architecture Doc",
            content="Architecture description " * 50,
            project="greymatter",
        )

        pat = patterns.create(
            type="process", domain="architecture",
            title="Always write ADRs",
            observation="Record architecture decisions",
        )
        for _ in range(4):
            s = sessions.start(project="greymatter")
            patterns.add_evidence(pat["id"], session_id=s["id"])
            sessions.end(s["id"])

        ctx = context_engine.build_session_context(
            project="greymatter", max_tokens=50000,
        )
        assert ctx["quality_standards"]
        assert len(ctx["active_patterns"]) >= 1
        assert isinstance(ctx["recent_context"], list)


# ---------------------------------------------------------------------------
# 4. Document Search Summary and Count
# ---------------------------------------------------------------------------

class TestDocumentSearchSummaryIntegration:
    """Tests document search_summary, count, and export_markdown."""

    def test_search_summary_returns_compact_results(self, doc_store):
        """search_summary returns truncated content previews."""
        long_content = "Important detail. " * 100
        doc_store.create(
            doc_type="research", title="Research Paper",
            content=long_content, project="greymatter",
            tags="ml,embeddings",
        )
        doc_store.create(
            doc_type="design", title="Design Doc",
            content="Short design.", project="greymatter",
        )

        result = doc_store.search_summary(project="greymatter")
        assert result["total"] == 2
        assert result["returned"] == 2
        for doc in result["documents"]:
            assert "content_preview" in doc
            assert len(doc["content_preview"]) <= 200

    def test_search_summary_filters(self, doc_store):
        """search_summary respects doc_type, project, and tag filters."""
        doc_store.create(
            doc_type="design", title="Design A",
            content="Design content", project="greymatter",
            tags="auth",
        )
        doc_store.create(
            doc_type="research", title="Research B",
            content="Research content", project="salesos",
        )

        by_type = doc_store.search_summary(doc_type="design")
        assert by_type["total"] == 1
        assert by_type["documents"][0]["title"] == "Design A"

        by_project = doc_store.search_summary(project="salesos")
        assert by_project["total"] == 1

        by_tag = doc_store.search_summary(tags="auth")
        assert by_tag["total"] == 1

    def test_count_filters(self, doc_store):
        """count() correctly filters by doc_type and project."""
        doc_store.create(
            doc_type="design", title="D1",
            content="c1", project="greymatter",
        )
        doc_store.create(
            doc_type="design", title="D2",
            content="c2", project="salesos",
        )
        doc_store.create(
            doc_type="research", title="R1",
            content="c3", project="greymatter",
        )

        assert doc_store.count() == 3
        assert doc_store.count(doc_type="design") == 2
        assert doc_store.count(project="greymatter") == 2
        assert doc_store.count(doc_type="design", project="greymatter") == 1

    def test_export_markdown_rendering(self, doc_store):
        """export_markdown produces well-formatted markdown with metadata."""
        doc = doc_store.create(
            doc_type="decision", title="Choose JWT",
            content="We chose JWT for authentication because it is stateless.",
            project="greymatter",
            tags="auth,security",
        )

        md = doc_store.export_markdown(doc["id"])
        assert "# Choose JWT" in md
        assert "**Type:** decision" in md
        assert "**Project:** greymatter" in md
        assert "auth,security" in md
        assert "We chose JWT for authentication" in md

    def test_export_markdown_nonexistent_returns_empty(self, doc_store):
        """export_markdown returns empty string for non-existent doc."""
        assert doc_store.export_markdown("doc-nonexistent") == ""

    def test_import_file_creates_document(self, doc_store, tmp_path):
        """import_file reads a markdown file and creates a document entry."""
        md_file = tmp_path / "test-doc.md"
        md_file.write_text("# Test Document\n\nThis is test content.\n")

        doc = doc_store.import_file(
            file_path=str(md_file),
            doc_type="design",
            project="greymatter",
            tags="imported",
        )

        assert doc["title"] == "test-doc"
        assert "# Test Document" in doc["content"]
        assert doc["doc_type"] == "design"
        assert doc["project"] == "greymatter"
        assert doc["tags"] == "imported"
        assert doc["token_estimate"] > 0


# ---------------------------------------------------------------------------
# 5. Knowledge Search Summary and Application Tracking
# ---------------------------------------------------------------------------

class TestKnowledgeSearchSummaryIntegration:
    """Tests knowledge search_summary and record_application interactions."""

    def test_search_summary_pagination(self, knowledge):
        """search_summary returns total count and paginated results."""
        for i in range(10):
            knowledge.create(
                entry_type="expertise",
                title=f"Expertise #{i}",
                content=f"Detailed content about topic {i} " * 20,
                domain="testing",
            )

        result = knowledge.search_summary(domain="testing", limit=5)
        assert result["total"] == 10
        assert result["returned"] == 5
        assert len(result["entries"]) == 5
        for entry in result["entries"]:
            assert "content_preview" in entry
            assert len(entry["content_preview"]) <= 200

    def test_search_summary_excludes_pruned(self, knowledge):
        """search_summary excludes pruned entries from results and count."""
        active = knowledge.create(
            entry_type="expertise", title="Active Entry",
            content="Still relevant", domain="api",
        )
        pruned_entry = knowledge.create(
            entry_type="gotcha", title="Pruned Entry",
            content="No longer relevant", domain="api",
        )
        knowledge.prune(pruned_entry["id"])

        result = knowledge.search_summary(domain="api")
        assert result["total"] == 1
        entry_ids = [e["id"] for e in result["entries"]]
        assert active["id"] in entry_ids
        assert pruned_entry["id"] not in entry_ids

    def test_search_summary_text_filter(self, knowledge):
        """search_summary text filter matches title and content."""
        knowledge.create(
            entry_type="expertise", title="SQLite WAL Mode",
            content="Write-ahead logging for concurrent access",
            domain="database",
        )
        knowledge.create(
            entry_type="gotcha", title="Connection Pooling",
            content="Use SQLite connection pools carefully",
            domain="database",
        )

        by_title = knowledge.search_summary(text="WAL")
        assert by_title["total"] == 1

        by_content = knowledge.search_summary(text="concurrent")
        assert by_content["total"] == 1

    def test_record_application_increments_count(self, knowledge):
        """record_application correctly tracks usage count."""
        entry = knowledge.create(
            entry_type="expertise", title="Used Knowledge",
            content="Applied in agent context",
            domain="orchestration",
        )
        assert knowledge.get(entry["id"])["applied_count"] == 0

        knowledge.record_application(entry["id"])
        assert knowledge.get(entry["id"])["applied_count"] == 1

        knowledge.record_application(entry["id"])
        knowledge.record_application(entry["id"])
        assert knowledge.get(entry["id"])["applied_count"] == 3

    def test_application_tracking_across_session_workflow(
        self, knowledge, sessions, context_engine,
    ):
        """Knowledge application tracking works in session context."""
        entry = knowledge.create(
            entry_type="expertise", title="Session Context Knowledge",
            content="Applied during context build",
            domain="testing",
        )

        session = sessions.start(project="greymatter")
        knowledge.record_application(entry["id"])

        updated = knowledge.get(entry["id"])
        assert updated["applied_count"] == 1
        assert updated["updated_at"] is not None

        sessions.end(session["id"], summary="Used knowledge in context")

        assert sessions.get(session["id"])["ended_at"] is not None


# ---------------------------------------------------------------------------
# 6. Session State Management and Checkpoints
# ---------------------------------------------------------------------------

class TestSessionStateManagement:
    """Tests session state transitions, checkpoints, and recovery patterns."""

    def test_session_state_transitions(self, sessions):
        """Session state can be updated through active -> compacting -> recovering."""
        session = sessions.start(project="greymatter")

        sessions.update_state(session["id"], "active", active_work_item_id="wi-001")
        s = sessions.get(session["id"])
        assert s["state"] == "active"
        assert s["active_work_item_id"] == "wi-001"

        sessions.update_state(session["id"], "compacting")
        s = sessions.get(session["id"])
        assert s["state"] == "compacting"

        sessions.update_state(session["id"], "recovering")
        s = sessions.get(session["id"])
        assert s["state"] == "recovering"

    def test_multiple_checkpoints_ordered(self, sessions, work_items):
        """Multiple checkpoints are stored and latest is retrievable."""
        session = sessions.start(project="greymatter")
        work = work_items.create(title="Checkpoint test task")

        sessions.update_state(
            session["id"], "active",
            active_work_item_id=work["id"],
        )

        cp1 = sessions.checkpoint(
            session["id"],
            context_summary="Working on step 1",
            open_items=json.dumps([work["id"]]),
        )
        assert "checkpoint_id" in cp1

        cp2 = sessions.checkpoint(
            session["id"],
            context_summary="Working on step 2",
            open_items=json.dumps([work["id"]]),
        )
        assert cp2["checkpoint_id"] != cp1["checkpoint_id"]

        latest = sessions.get_latest_checkpoint(session["id"])
        assert latest is not None
        assert latest["context_summary"] == "Working on step 2"

    def test_checkpoint_invalid_session(self, sessions):
        """Checkpointing a non-existent session returns error."""
        result = sessions.checkpoint("nonexistent-session")
        assert "error" in result

    def test_compaction_counter_across_checkpoints(self, sessions):
        """Compaction counter increments independently of checkpoints."""
        session = sessions.start(project="greymatter")

        c1 = sessions.increment_compaction(session["id"])
        assert c1 == 1

        sessions.checkpoint(
            session["id"],
            context_summary="After first compaction",
        )

        c2 = sessions.increment_compaction(session["id"])
        assert c2 == 2

        latest = sessions.get_latest_checkpoint(session["id"])
        assert latest is not None
        assert latest["compaction_count"] == 1

    def test_session_checkpoint_recovery_workflow(
        self, sessions, work_items, knowledge,
    ):
        """Full checkpoint -> recovery workflow preserves context."""
        session = sessions.start(project="greymatter")
        work = work_items.create(
            title="Long-running task",
            description="Multi-step implementation",
            project="greymatter",
        )
        work_items.update_status(work["id"], "in_progress")

        sessions.update_state(
            session["id"], "active",
            active_work_item_id=work["id"],
        )
        sessions.checkpoint(
            session["id"],
            context_summary="Halfway through implementation. Added auth module.",
            open_items=json.dumps([work["id"]]),
        )

        knowledge.create(
            entry_type="gotcha", title="Auth token edge case",
            content="Expired tokens cause 500 instead of 401",
            domain="auth",
        )

        sessions.update_state(session["id"], "compacting")
        sessions.increment_compaction(session["id"])

        sessions.checkpoint(
            session["id"],
            context_summary="After compaction. Auth module done, testing remaining.",
            open_items=json.dumps([work["id"]]),
        )

        latest = sessions.get_latest_checkpoint(session["id"])
        assert "After compaction" in latest["context_summary"]

        open_items = json.loads(latest["open_items"])
        assert work["id"] in open_items

        sessions.update_state(session["id"], "active")
        work_items.update_status(work["id"], "completed")
        sessions.end(session["id"], summary="Completed auth implementation")

        ended = sessions.get(session["id"])
        assert ended["ended_at"] is not None
        assert ended["compaction_count"] == 1


# ---------------------------------------------------------------------------
# 7. Cross-Module Write Pipeline
# ---------------------------------------------------------------------------

class TestCrossModuleWritePipeline:
    """Tests write operations spanning multiple modules in realistic workflows."""

    def test_knowledge_creation_with_quality_and_context(
        self, knowledge, quality, standards, doc_store,
        context_engine, sessions, patterns,
    ):
        """Full pipeline: session -> knowledge -> quality check -> context build."""
        standards.seed_defaults()
        session = sessions.start(project="greymatter")

        knowledge.create(
            entry_type="gotcha", title="SQLite PRAGMA ordering",
            content="PRAGMA journal_mode must be set before any writes",
            domain="database",
        )
        knowledge.create(
            entry_type="expertise", title="WAL mode concurrency",
            content="WAL allows concurrent reads during writes",
            domain="database",
        )

        doc_store.create(
            doc_type="session_log",
            title=f"Session {session['id']}",
            content="Investigated SQLite PRAGMA ordering and WAL mode.",
            project="greymatter",
        )

        report = quality.full_check()
        assert report.total_entries >= 2

        ctx = context_engine.build_session_context(project="greymatter")
        assert ctx["quality_standards"]
        assert isinstance(ctx["recent_context"], list)
        assert len(ctx["recent_context"]) >= 1

        sessions.end(session["id"], summary="Documented SQLite patterns")

    def test_pattern_evidence_with_knowledge_capture_and_staleness(
        self, db, knowledge, patterns, sessions, work_items,
    ):
        """Pattern -> evidence -> knowledge -> age -> staleness -> confirm lifecycle."""
        session = sessions.start(project="greymatter")
        work = work_items.create(
            title="Document retry patterns", project="greymatter",
        )

        pat = patterns.create(
            type="process", domain="resilience",
            title="Exponential backoff",
            observation="Use exponential backoff for transient failures",
        )
        patterns.add_evidence(
            pat["id"], session_id=session["id"], work_item_id=work["id"],
        )

        gotcha = knowledge.create(
            entry_type="gotcha",
            title="Retry storm risk",
            content="Without jitter, retries synchronize and cause storms",
            domain="resilience",
        )

        work_items.update_status(work["id"], "completed")
        sessions.end(session["id"], summary="Documented retry patterns")

        _backdate_entry(db, gotcha["id"], days=120)
        stale = knowledge.flag_stale(stale_days=90)
        assert len(stale) == 1

        knowledge.confirm(gotcha["id"])
        assert len(knowledge.get_stale()) == 0

        confirmed = knowledge.get(gotcha["id"])
        assert confirmed["last_confirmed_at"] is not None

    def test_soul_evolution_with_session_and_knowledge(
        self, soul, sessions, knowledge, patterns,
    ):
        """Soul document grows alongside session activity and knowledge capture."""
        soul.create_initial_soul("Keith")
        count_before = soul.increment_session_count()

        session = sessions.start(project="greymatter")

        knowledge.create(
            entry_type="expertise",
            title="Agent prompt engineering",
            content="Use structured output schemas for reliable agent responses",
            domain="orchestration",
        )

        pat = patterns.create(
            type="preference", domain="collaboration",
            title="Explain before acting",
            observation="User prefers agents explain their plan before executing",
        )
        patterns.add_evidence(pat["id"], session_id=session["id"])

        soul.add_learned_preference(
            "User prefers detailed explanations before code changes",
        )
        soul.add_relationship_moment(
            "Collaborated on agent prompt engineering — good session",
        )

        sessions.end(session["id"], summary="Agent prompt engineering session")
        count_after = soul.increment_session_count()

        assert count_after == count_before + 1

        content = soul.load()
        assert "detailed explanations" in content
        assert "Collaborated on agent prompt engineering" in content

        api_knowledge = knowledge.search(domain="orchestration")
        assert len(api_knowledge) >= 1

    def test_work_item_review_with_knowledge_and_quality(
        self, work_items, knowledge, quality, sessions,
    ):
        """Work item review lifecycle integrates with knowledge capture."""
        session = sessions.start(project="greymatter")
        work = work_items.create(
            title="Implement input validation",
            description="Add Pydantic models for API input validation",
            priority=10,
            domain_tags="api,security",
            project="greymatter",
        )

        work_items.update_status(work["id"], "in_progress")

        knowledge.create(
            entry_type="gotcha",
            title="Pydantic v2 model_validator ordering",
            content="model_validator(mode='before') runs before field validation",
            domain="api",
        )

        work_items.update_status(work["id"], "pending_review")
        reviewed = work_items.update_review(
            work["id"],
            status="approved",
            review_notes="Input validation complete. Tests pass.",
        )
        assert reviewed["status"] == "approved"

        sessions.end(session["id"], summary="Input validation implemented")

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

        gotchas = knowledge.search(entry_type="gotcha", domain="api")
        assert len(gotchas) == 1
        assert "model_validator" in gotchas[0]["title"]
