"""Build integration tests: cross-module workflows that verify GreyMatter
core modules work together as a coherent system.

Covers integration points not exercised by existing end-to-end tests:
- SessionStateManager + SessionManager: structured state persistence across compaction
- Observation dedup: duplicate detection and merge with real schema
- Quality engine + knowledge + patterns: quality scoring on mixed content
- Knowledge classification: personal vs technical entry isolation
- Pattern confidence escalation → context engine visibility
- Session state tiered formatting: budget-based recovery injection
- Work item → pattern evidence → knowledge promotion pipeline
"""

import json
import sqlite3
from datetime import datetime, timedelta, timezone

import pytest

from greymatter_core.context import ContextEngine
from greymatter_core.db import SQLiteDatabase
from greymatter_core.dedup import (
    dedup_observations,
    increment_observation,
    should_deduplicate,
)
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.session_state import (
    MAX_STATES_PER_SESSION,
    TIER_ESSENTIAL,
    TIER_FULL,
    TIER_STANDARD,
    PlanStep,
    SessionState,
    SessionStateManager,
)
from greymatter_core.sessions import SessionManager
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 sessions(db):
    return SessionManager(db)


@pytest.fixture
def session_states(db):
    return SessionStateManager(db)


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


@pytest.fixture
def patterns(db):
    return PatternManager(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 dedup_db(db):
    """Return raw sqlite3 connection for dedup functions (they use raw sqlite3)."""
    conn = sqlite3.connect(":memory:")
    conn.executescript("""
        CREATE TABLE IF NOT EXISTS observations (
            id TEXT PRIMARY KEY,
            session_id TEXT DEFAULT 'test',
            trigger TEXT DEFAULT 'test',
            category TEXT DEFAULT 'insight',
            title TEXT NOT NULL,
            content TEXT DEFAULT '',
            domains TEXT DEFAULT 'general',
            confidence REAL DEFAULT 0.5,
            pruned INTEGER DEFAULT 0,
            created_at TEXT DEFAULT (datetime('now')),
            updated_at TEXT DEFAULT (datetime('now'))
        );
        CREATE VIRTUAL TABLE IF NOT EXISTS observations_fts
            USING fts5(title, content);
    """)
    yield conn
    conn.close()


# ---------------------------------------------------------------------------
# 1. SessionState + SessionManager Integration
# ---------------------------------------------------------------------------

class TestSessionStateLifecycle:
    """Verify SessionStateManager works with real sessions from SessionManager."""

    def test_save_and_load_across_compactions(self, sessions, session_states):
        """State saved at compaction N can be loaded back with correct data."""
        session = sessions.start(project="greymatter")
        sid = session["id"]

        state0 = SessionState(
            session_id=sid,
            compaction_number=0,
            current_task="Implementing rate limiter",
            plan_title="Rate Limiter Implementation",
            plan_steps=[
                PlanStep(description="Design token bucket", status="completed"),
                PlanStep(description="Write core logic", status="in_progress"),
                PlanStep(description="Add tests", status="pending"),
            ],
            current_step_index=1,
            progress_summary="Token bucket design finalized",
            next_steps="Implement core rate limiting logic",
        )
        result0 = session_states.save(state0)
        assert result0["session_id"] == sid

        state1 = SessionState(
            session_id=sid,
            compaction_number=1,
            current_task="Implementing rate limiter",
            plan_title="Rate Limiter Implementation",
            plan_steps=[
                PlanStep(description="Design token bucket", status="completed"),
                PlanStep(description="Write core logic", status="completed"),
                PlanStep(description="Add tests", status="in_progress"),
            ],
            current_step_index=2,
            progress_summary="Core logic done, writing tests",
            next_steps="Finish pytest integration tests",
            decisions=[{
                "decision": "Use asyncio.Semaphore for token bucket",
                "rationale": "Simpler than custom implementation",
            }],
        )
        result1 = session_states.save(state1)
        assert result1["session_id"] == sid

        latest = session_states.load_latest(sid)
        assert latest is not None
        assert latest.compaction_number == 1
        assert latest.current_step_index == 2
        assert latest.plan_steps[1].status == "completed"
        assert len(latest.decisions) == 1

        older = session_states.load_by_compaction(sid, 0)
        assert older is not None
        assert older.compaction_number == 0
        assert older.current_step_index == 1
        assert older.plan_steps[1].status == "in_progress"

    def test_state_retention_limit_enforced(self, sessions, session_states):
        """Only MAX_STATES_PER_SESSION states are retained per session."""
        session = sessions.start(project="greymatter")
        sid = session["id"]

        for i in range(MAX_STATES_PER_SESSION + 3):
            state = SessionState(
                session_id=sid,
                compaction_number=i,
                current_task=f"Task at compaction {i}",
            )
            session_states.save(state)

        history = session_states.get_history(sid)
        assert len(history) == MAX_STATES_PER_SESSION

        compaction_numbers = [h["compaction_number"] for h in history]
        total_saved = MAX_STATES_PER_SESSION + 3
        assert total_saved - 1 in compaction_numbers

    def test_session_end_preserves_state(self, sessions, session_states):
        """Ending a session does not affect stored session states."""
        session = sessions.start(project="greymatter")
        sid = session["id"]

        state = SessionState(
            session_id=sid,
            compaction_number=0,
            current_task="Work before session end",
            blockers=["Waiting for API response"],
        )
        session_states.save(state)

        sessions.end(sid, summary="Done for now")

        loaded = session_states.load_latest(sid)
        assert loaded is not None
        assert loaded.current_task == "Work before session end"
        assert loaded.blockers == ["Waiting for API response"]

    def test_format_injection_tiers(self, sessions, session_states):
        """format_for_injection respects budget tiers."""
        session = sessions.start(project="greymatter")
        sid = session["id"]

        state = SessionState(
            session_id=sid,
            compaction_number=2,
            current_task="Complex migration task",
            plan_title="Database Migration",
            plan_steps=[
                PlanStep(description="Backup", status="completed"),
                PlanStep(description="Migrate schema", status="in_progress"),
                PlanStep(description="Verify data", status="pending"),
                PlanStep(description="Update indexes", status="pending"),
            ],
            current_step_index=1,
            decisions=[
                {"decision": "Use WAL mode", "rationale": "Better concurrency"},
                {"decision": "Batch inserts", "rationale": "Performance"},
            ],
            active_files=[
                {"path": "core/db.py", "role": "editing"},
                {"path": "core/schema.py", "role": "reading"},
            ],
            progress_summary="Backup complete, schema migration in progress",
            next_steps="Finish schema migration and verify integrity",
            blockers=["Need to handle foreign key constraints"],
            tasks=[
                {"content": "Write migration script", "status": "completed"},
                {"content": "Test rollback", "status": "pending"},
            ],
            custom={"migration_version": "v2.1"},
        )

        essential = state.format_for_injection(max_chars=TIER_ESSENTIAL)
        standard = state.format_for_injection(max_chars=TIER_STANDARD)
        full = state.format_for_injection(max_chars=TIER_FULL)

        assert "Complex migration task" in essential
        assert len(essential) <= TIER_ESSENTIAL

        assert len(standard) >= len(essential)
        assert len(standard) <= TIER_STANDARD

        assert len(full) >= len(standard)
        assert len(full) <= TIER_FULL

    def test_merge_conversation_state(self, sessions, session_states):
        """merge_conversation_state fills gaps from regex-extracted state."""
        session = sessions.start(project="greymatter")

        state = SessionState(
            session_id=session["id"],
            compaction_number=0,
            active_files=[{"path": "core/db.py", "role": "editing"}],
        )

        conversation_state = {
            "current_task": "Fixing transaction isolation",
            "next_steps": "Run integration tests",
            "recent_files": ["core/db.py", "core/tests/test_db.py"],
        }

        merged = session_states.merge_conversation_state(state, conversation_state)
        assert merged.current_task == "Fixing transaction isolation"
        assert merged.next_steps == "Run integration tests"
        paths = [f["path"] for f in merged.active_files]
        assert "core/db.py" in paths
        assert "core/tests/test_db.py" in paths
        assert paths.count("core/db.py") == 1


# ---------------------------------------------------------------------------
# 2. Observation Dedup Integration
# ---------------------------------------------------------------------------

class TestDedupIntegration:
    """Verify dedup functions work with real observation data."""

    def test_dedup_merges_same_title_within_window(self, dedup_db):
        """Observations with same title within 5 minutes are merged."""
        now = datetime.now(timezone.utc)
        base_ts = now.isoformat()
        close_ts = (now + timedelta(minutes=2)).isoformat()
        far_ts = (now + timedelta(minutes=10)).isoformat()

        dedup_db.executemany(
            """INSERT INTO observations (id, title, content, created_at, updated_at)
               VALUES (?, ?, ?, ?, ?)""",
            [
                ("obs-1", "Always check JWT", '{"detail":"first"}', base_ts, base_ts),
                ("obs-2", "Always check JWT", '{"detail":"second"}', close_ts, close_ts),
                ("obs-3", "Always check JWT", '{"detail":"far"}', far_ts, far_ts),
            ],
        )
        dedup_db.commit()

        deleted = dedup_observations(dedup_db, window_minutes=5)
        assert deleted == 1

        remaining = dedup_db.execute(
            "SELECT id FROM observations ORDER BY created_at"
        ).fetchall()
        remaining_ids = [r[0] for r in remaining]
        assert "obs-1" in remaining_ids
        assert "obs-2" not in remaining_ids
        assert "obs-3" in remaining_ids

        keeper = dedup_db.execute(
            "SELECT content FROM observations WHERE id = 'obs-1'"
        ).fetchone()
        content = json.loads(keeper[0])
        assert content["dedup_count"] == 2

    def test_dedup_dry_run_does_not_modify(self, dedup_db):
        """dry_run=True counts but does not delete."""
        now = datetime.now(timezone.utc)
        ts1 = now.isoformat()
        ts2 = (now + timedelta(minutes=1)).isoformat()

        dedup_db.executemany(
            """INSERT INTO observations (id, title, content, created_at, updated_at)
               VALUES (?, ?, ?, ?, ?)""",
            [
                ("obs-dry-1", "Repeat title", "c1", ts1, ts1),
                ("obs-dry-2", "Repeat title", "c2", ts2, ts2),
            ],
        )
        dedup_db.commit()

        would_delete = dedup_observations(dedup_db, window_minutes=5, dry_run=True)
        assert would_delete == 1

        count = dedup_db.execute("SELECT COUNT(*) FROM observations").fetchone()[0]
        assert count == 2

    def test_should_deduplicate_finds_recent(self, dedup_db):
        """should_deduplicate returns existing ID for recent same-title observation."""
        now = datetime.now(timezone.utc).isoformat()
        dedup_db.execute(
            """INSERT INTO observations (id, title, content, created_at)
               VALUES (?, ?, ?, ?)""",
            ("obs-recent", "Connection timeout handling", "Detail", now),
        )
        dedup_db.commit()

        existing_id = should_deduplicate(
            dedup_db, "Connection timeout handling", window_minutes=5
        )
        assert existing_id == "obs-recent"

        no_match = should_deduplicate(
            dedup_db, "Something else entirely", window_minutes=5
        )
        assert no_match is None

    def test_increment_observation_updates_count(self, dedup_db):
        """increment_observation bumps dedup_count and updates content."""
        dedup_db.execute(
            """INSERT INTO observations (id, title, content, created_at)
               VALUES (?, ?, ?, ?)""",
            ("obs-inc", "Rate limit pattern", '{"detail":"original"}',
             datetime.now(timezone.utc).isoformat()),
        )
        dedup_db.commit()

        increment_observation(
            dedup_db, "obs-inc", '{"detail":"updated","extra":"field"}'
        )

        row = dedup_db.execute(
            "SELECT content FROM observations WHERE id = 'obs-inc'"
        ).fetchone()
        content = json.loads(row[0])
        assert content["dedup_count"] == 2
        assert content["detail"] == "updated"
        assert content["extra"] == "field"

        increment_observation(dedup_db, "obs-inc", '{"detail":"third"}')
        row2 = dedup_db.execute(
            "SELECT content FROM observations WHERE id = 'obs-inc'"
        ).fetchone()
        content2 = json.loads(row2[0])
        assert content2["dedup_count"] == 3


# ---------------------------------------------------------------------------
# 3. Quality Engine + Knowledge + Patterns Cross-Module
# ---------------------------------------------------------------------------

class TestQualityKnowledgePatternIntegration:
    """Quality engine detects issues across knowledge entries and patterns."""

    def test_quality_detects_near_duplicates(self, knowledge, quality):
        """Quality engine flags near-duplicate knowledge entries."""
        knowledge.create(
            entry_type="gotcha",
            title="Always use parameterized queries",
            content="Parameterized queries prevent SQL injection attacks",
            domain="security",
        )
        knowledge.create(
            entry_type="gotcha",
            title="Always use parameterized queries for SQL",
            content="Parameterized queries prevent SQL injection",
            domain="security",
        )

        report = quality.full_check()
        assert report.total_entries >= 2
        duplicate_issues = [i for i in report.issues if i.issue_type == "duplicate"]
        assert len(duplicate_issues) >= 1

    def test_quality_score_degrades_with_issues(self, knowledge, quality):
        """Quality score decreases when issues are detected."""
        knowledge.create(
            entry_type="expertise",
            title="Unique entry",
            content="No duplicates here",
            domain="testing",
        )
        clean_report = quality.full_check()

        knowledge.create(
            entry_type="expertise",
            title="Always use global state",
            content="Global state is the best approach for sharing data",
            domain="architecture",
        )
        knowledge.create(
            entry_type="correction",
            title="Never use global state",
            content="Global state leads to coupling and testing difficulties",
            domain="architecture",
        )

        dirty_report = quality.full_check()
        assert dirty_report.score <= clean_report.score

    def test_quality_with_mixed_sources(self, knowledge, patterns, sessions, quality):
        """Quality check handles knowledge from multiple creation paths."""
        knowledge.create(
            entry_type="reference",
            title="Python 3.12 features",
            content="Type parameter syntax, f-string improvements",
            domain="knowledge",
        )

        pat = patterns.create(
            type="convention", domain="testing",
            title="Prefer pytest fixtures over setUp",
            observation="Fixtures are more composable and readable",
        )
        for _ in range(5):
            s = sessions.start(project="greymatter")
            patterns.add_evidence(pat["id"], session_id=s["id"])
            sessions.end(s["id"])

        report = quality.full_check()
        assert report.total_entries >= 1
        assert isinstance(report.score, float)
        assert 0.0 <= report.score <= 1.0


# ---------------------------------------------------------------------------
# 4. Knowledge Classification Integration
# ---------------------------------------------------------------------------

class TestKnowledgeClassificationIntegration:
    """Personal vs technical knowledge entries have correct isolation."""

    def test_personal_entries_get_cognitive_classification(self, knowledge):
        """Personal entry types auto-set to CLASSIFICATION_COGNITIVE."""
        mood = knowledge.create(
            entry_type="mood",
            title="Energized after code review",
            content="Felt great after productive review session",
        )
        assert mood["classification_level"] == CLASSIFICATION_COGNITIVE

        goal = knowledge.create(
            entry_type="goal",
            title="Learn Rust FFI",
            content="Bridge Python orchestration with Rust for performance",
        )
        assert goal["classification_level"] == CLASSIFICATION_COGNITIVE

    def test_technical_entries_get_personal_classification(self, knowledge):
        """Technical entry types default to CLASSIFICATION_PERSONAL."""
        gotcha = knowledge.create(
            entry_type="gotcha",
            title="SQLite WAL requires shared memory",
            content="NFS mounts break WAL mode silently",
            domain="infrastructure",
        )
        assert gotcha["classification_level"] == CLASSIFICATION_PERSONAL

    def test_explicit_classification_overrides_default(self, knowledge):
        """Explicit classification_level overrides the auto-set default."""
        entry = knowledge.create(
            entry_type="reference",
            title="Public API documentation",
            content="REST API endpoints are documented at /docs",
            classification_level=CLASSIFICATION_OPEN,
        )
        assert entry["classification_level"] == CLASSIFICATION_OPEN

    def test_search_returns_all_classifications(self, knowledge):
        """Search includes entries of all classification levels."""
        knowledge.create(
            entry_type="expertise",
            title="AsyncIO internals",
            content="Event loop implementation details",
            domain="knowledge",
        )
        knowledge.create(
            entry_type="mood",
            title="Focused state",
            content="Deep work session",
            domain="personal",
        )

        all_entries = knowledge.search()
        assert len(all_entries) >= 2

    def test_prune_and_search_exclusion(self, knowledge):
        """Pruned entries are excluded from search by default."""
        entry = knowledge.create(
            entry_type="reference",
            title="Outdated API endpoint",
            content="This endpoint was deprecated",
            domain="knowledge",
        )
        knowledge.prune(entry["id"])

        results = knowledge.search(text="Outdated")
        assert len(results) == 0

        results_with_pruned = knowledge.search(text="Outdated", include_pruned=True)
        assert len(results_with_pruned) == 1


# ---------------------------------------------------------------------------
# 5. Pattern Escalation → Context Engine Visibility
# ---------------------------------------------------------------------------

class TestPatternEscalationContextIntegration:
    """Full pipeline: pattern creation → evidence → escalation → context visibility."""

    def test_pattern_invisible_until_medium_confidence(
        self, patterns, sessions, context_engine, standards
    ):
        """Patterns only appear in session context after reaching medium confidence."""
        standards.seed_defaults()

        pat = patterns.create(
            type="process", domain="orchestration",
            title="Circuit breaker for external APIs",
            observation="Wrap all external API calls with circuit breaker",
        )

        ctx = context_engine.build_session_context()
        pat_ids = [p["id"] for p in ctx["active_patterns"]]
        assert pat["id"] not in pat_ids

        for _ in range(3):
            s = sessions.start(project="greymatter")
            patterns.add_evidence(pat["id"], session_id=s["id"])
            sessions.end(s["id"])

        refreshed = patterns.get(pat["id"])
        assert refreshed["confidence"] == "medium"

        ctx_after = context_engine.build_session_context()
        pat_ids_after = [p["id"] for p in ctx_after["active_patterns"]]
        assert pat["id"] in pat_ids_after

    def test_multiple_patterns_sorted_by_confidence(
        self, patterns, sessions, context_engine, standards
    ):
        """Context engine returns patterns ordered by confidence level."""
        standards.seed_defaults()

        pat_med = patterns.create(
            type="convention", domain="testing",
            title="Use parametrize for data-driven",
            observation="pytest.mark.parametrize for input variations",
        )
        pat_high = patterns.create(
            type="convention", domain="testing",
            title="Fixtures over setUp",
            observation="Prefer pytest fixtures for dependency injection",
        )

        for _ in range(3):
            s = sessions.start(project="greymatter")
            patterns.add_evidence(pat_med["id"], session_id=s["id"])
            sessions.end(s["id"])

        for _ in range(7):
            s = sessions.start(project="greymatter")
            patterns.add_evidence(pat_high["id"], session_id=s["id"])
            sessions.end(s["id"])

        assert patterns.get(pat_med["id"])["confidence"] == "medium"
        assert patterns.get(pat_high["id"])["confidence"] == "high"

        ctx = context_engine.build_session_context()
        pat_list = ctx["active_patterns"]
        assert len(pat_list) >= 2

        ids_in_order = [p["id"] for p in pat_list]
        high_idx = ids_in_order.index(pat_high["id"])
        med_idx = ids_in_order.index(pat_med["id"])
        assert high_idx < med_idx


# ---------------------------------------------------------------------------
# 6. Work Item → Pattern Evidence → Knowledge Pipeline
# ---------------------------------------------------------------------------

class TestWorkPatternKnowledgePipeline:
    """Full pipeline: work item drives pattern evidence which informs knowledge."""

    def test_work_item_bound_pattern_evidence(
        self, work_items, patterns, sessions, knowledge
    ):
        """Pattern evidence linked to work items creates traceable lineage."""
        session = sessions.start(project="greymatter")
        work = work_items.create(
            title="Add circuit breaker to dispatch",
            priority=20,
            domain_tags="orchestration,reliability",
        )
        work_items.update_status(work["id"], "in_progress")

        pat = patterns.create(
            type="process", domain="orchestration",
            title="Circuit breaker state machine",
            observation="Open → half-open → closed with configurable thresholds",
        )

        for _ in range(3):
            s = sessions.start(project="greymatter")
            patterns.add_evidence(
                pat["id"], session_id=s["id"], work_item_id=work["id"]
            )
            sessions.end(s["id"])

        escalated = patterns.get(pat["id"])
        assert escalated["confidence"] == "medium"

        kn = knowledge.create(
            entry_type="expertise",
            title="Circuit breaker thresholds",
            content="5 failures to open, 30s half-open probe, 3 successes to close",
            domain="orchestration",
            tags="circuit-breaker,reliability",
        )

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

        assert knowledge.get(kn["id"]) is not None
        assert patterns.get(pat["id"])["confidence"] == "medium"
        assert work_items.get(work["id"])["status"] == "completed"

        sessions.end(session["id"], summary="Implemented circuit breaker")

    def test_multiple_work_items_share_pattern(
        self, work_items, patterns, sessions
    ):
        """Multiple work items can provide evidence for the same pattern."""
        pat = patterns.create(
            type="convention", domain="api",
            title="Idempotency keys for mutations",
            observation="All mutating API calls should accept idempotency keys",
        )

        for i in range(3):
            work = work_items.create(
                title=f"API endpoint {i}", priority=10 + i
            )
            s = sessions.start(project="greymatter")
            patterns.add_evidence(
                pat["id"], session_id=s["id"], work_item_id=work["id"]
            )
            sessions.end(s["id"])

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


# ---------------------------------------------------------------------------
# 7. Knowledge Update and Search Summary
# ---------------------------------------------------------------------------

class TestKnowledgeUpdateSearchIntegration:
    """Knowledge update + search_summary integration."""

    def test_update_then_search_reflects_changes(self, knowledge):
        """Updated entries are found by their new content."""
        entry = knowledge.create(
            entry_type="reference",
            title="Deployment checklist",
            content="1. Run tests 2. Build binary",
            domain="infrastructure",
        )

        knowledge.update(entry["id"], content="1. Run tests 2. Build binary 3. Sign artifacts")

        results = knowledge.search(text="Sign artifacts")
        assert len(results) == 1
        assert results[0]["id"] == entry["id"]

    def test_search_summary_returns_preview(self, knowledge):
        """search_summary returns truncated content previews."""
        long_content = "Detailed explanation of the token bucket algorithm. " * 20
        knowledge.create(
            entry_type="expertise",
            title="Token bucket deep dive",
            content=long_content,
            domain="api",
        )

        summary = knowledge.search_summary(domain="api")
        assert summary["total"] == 1
        assert summary["returned"] == 1
        assert len(summary["entries"][0]["content_preview"]) <= 200

    def test_search_by_multiple_filters(self, knowledge):
        """Search with combined entry_type and domain filters."""
        knowledge.create(
            entry_type="gotcha", title="G1",
            content="Gotcha in security", domain="security",
        )
        knowledge.create(
            entry_type="gotcha", title="G2",
            content="Gotcha in api", domain="api",
        )
        knowledge.create(
            entry_type="reference", title="R1",
            content="Reference in security", domain="security",
        )

        security_gotchas = knowledge.search(entry_type="gotcha", domain="security")
        assert len(security_gotchas) == 1
        assert security_gotchas[0]["title"] == "G1"


# ---------------------------------------------------------------------------
# 8. Standards + Context + Documents Combined
# ---------------------------------------------------------------------------

class TestStandardsContextDocsCombined:
    """Full context build with standards, patterns, and documents together."""

    def test_full_context_includes_all_sources(
        self, standards, doc_store, patterns, sessions, context_engine
    ):
        """Context engine combines all sources into a coherent session context."""
        standards.seed_defaults()

        doc_store.create(
            doc_type="design",
            title="System Architecture",
            content="GreyMatter uses SQLite for local persistence " * 30,
            project="greymatter",
        )

        pat = patterns.create(
            type="convention", domain="database",
            title="WAL mode for concurrency",
            observation="SQLite WAL mode enables concurrent reads",
        )
        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")

        assert ctx["quality_standards"]
        assert ctx["active_patterns"]
        assert len(ctx["active_patterns"]) >= 1

        standards_data = json.loads(ctx["quality_standards"])
        assert isinstance(standards_data, list)
        assert all("rule" in s for s in standards_data)
