"""Integration tests: write-path validation for uncovered modules.

Validates write operations that are not covered by existing integration
tests (test_write_integration.py, test_core_write_integration.py):

- StandardsManager write lifecycle (create, deactivate, seed_defaults)
- ContextEngine write operations (record_request, evaluate_relevance)
- PolicyEngine in-memory write operations (add_rule, remove_rule)
- ProactiveRecall.record_review across knowledge, observations, patterns
- Work item update_review edge cases and idempotency
- Cross-module pipeline: Standards → Context → Quality integration
"""

import json
import os
import sqlite3
from datetime import datetime, timedelta, timezone
from typing import Any

import pytest

from greymatter_core.context import ContextEngine
from greymatter_core.db import SQLiteDatabase
from greymatter_core.documents import DocumentStore
from greymatter_core.knowledge import KnowledgeStore
from greymatter_core.patterns import PatternManager
from greymatter_core.policy import (
    Condition,
    EvalContext,
    PolicyEngine,
    PolicyOutcome,
    PolicyRule,
)
from greymatter_core.quality import QualityEngine
from greymatter_core.recall import ProactiveRecall
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 write validation 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 recall_db(tmp_path):
    """Create a temporary SQLite database with full schema for ProactiveRecall."""
    db_path = str(tmp_path / "greymatter.db")
    # Use SQLiteDatabase to create schema, then close so recall can open its own connection
    database = SQLiteDatabase(db_path)
    database.close()
    return db_path


@pytest.fixture
def recall(recall_db):
    """ProactiveRecall instance backed by temp DB."""
    r = ProactiveRecall(db_path=recall_db)
    yield r
    r.close()


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


def _raw_query(db: SQLiteDatabase, sql: str, params: tuple = ()) -> list[dict]:
    """Execute raw SQL and return results as list of dicts."""
    return db.execute(sql, params)


def _raw_query_one(db: SQLiteDatabase, sql: str, params: tuple = ()) -> dict | None:
    """Execute raw SQL and return first row as dict."""
    return db.execute_one(sql, params)


def _seed_knowledge_in_recall_db(db_path: str, entry_id: str, title: str = "Test") -> None:
    """Insert a knowledge entry directly into the recall DB."""
    conn = sqlite3.connect(db_path)
    conn.execute(
        """INSERT INTO knowledge_entries
           (id, entry_type, title, content, applied_count, created_at, updated_at)
           VALUES (?, 'expertise', ?, 'test content', 0, datetime('now'), datetime('now'))""",
        (entry_id, title),
    )
    conn.commit()
    conn.close()


def _seed_observation_in_recall_db(
    db_path: str, obs_id: str, session_id: str = "sess-test",
) -> None:
    """Insert an observation directly into the recall DB."""
    conn = sqlite3.connect(db_path)
    conn.execute(
        """INSERT INTO observations
           (id, session_id, trigger, category, title, content,
            confidence, stability_days, review_count, created_at, updated_at)
           VALUES (?, ?, 'manual', 'insight', 'Test Obs', 'obs content',
                   0.5, 45, 0, datetime('now'), datetime('now'))""",
        (obs_id, session_id),
    )
    conn.commit()
    conn.close()


def _seed_pattern_in_recall_db(db_path: str, pat_id: str) -> None:
    """Insert a pattern directly into the recall DB."""
    conn = sqlite3.connect(db_path)
    conn.execute(
        """INSERT INTO patterns
           (id, type, domain, title, observation, confidence, confirmations,
            applications, created_at, updated_at)
           VALUES (?, 'process', 'testing', 'Test Pat', 'test observation',
                   'low', 0, 0, datetime('now'), datetime('now'))""",
        (pat_id,),
    )
    conn.commit()
    conn.close()


# ---------------------------------------------------------------------------
# 1. StandardsManager Write Lifecycle
# ---------------------------------------------------------------------------


class TestStandardsWriteLifecycle:
    """Verify StandardsManager create, deactivate, and seed_defaults writes."""

    def test_create_persists_all_fields(self, db, standards):
        """create() stores all fields in the quality_standards table."""
        std = standards.create(
            standard_id="std-test-001",
            category="code_quality",
            rule="All functions must have type annotations",
            severity="critical",
            example_good="def foo(x: int) -> str:",
            example_bad="def foo(x):",
            rationale="Type annotations catch bugs at review time",
        )
        assert std is not None
        assert std["id"] == "std-test-001"

        row = _raw_query_one(
            db, "SELECT * FROM quality_standards WHERE id = ?", ("std-test-001",)
        )
        assert row is not None
        assert row["category"] == "code_quality"
        assert row["rule"] == "All functions must have type annotations"
        assert row["severity"] == "critical"
        assert row["example_good"] == "def foo(x: int) -> str:"
        assert row["example_bad"] == "def foo(x):"
        assert row["rationale"] == "Type annotations catch bugs at review time"
        assert row["active"] == 1
        assert row["created_at"] is not None

    def test_create_with_optional_fields_none(self, db, standards):
        """create() works with optional fields omitted."""
        std = standards.create(
            standard_id="std-test-002",
            category="testing",
            rule="Tests must not use time.sleep",
            severity="important",
        )
        row = _raw_query_one(
            db, "SELECT * FROM quality_standards WHERE id = ?", ("std-test-002",)
        )
        assert row is not None
        assert row["example_good"] is None
        assert row["example_bad"] is None
        assert row["rationale"] is None
        assert row["active"] == 1

    def test_deactivate_sets_active_to_zero(self, db, standards):
        """deactivate() marks a standard inactive."""
        standards.create(
            standard_id="std-deact-001",
            category="security",
            rule="Never log secrets",
            severity="critical",
        )
        standards.deactivate("std-deact-001")

        row = _raw_query_one(
            db, "SELECT active FROM quality_standards WHERE id = ?", ("std-deact-001",)
        )
        assert row["active"] == 0

    def test_deactivated_standard_excluded_from_active(self, standards):
        """Deactivated standards do not appear in get_active()."""
        standards.create(
            standard_id="std-active-001",
            category="code",
            rule="Active rule",
            severity="important",
        )
        standards.create(
            standard_id="std-inactive-001",
            category="code",
            rule="Inactive rule",
            severity="important",
        )
        standards.deactivate("std-inactive-001")

        active = standards.get_active()
        active_ids = [s["id"] for s in active]
        assert "std-active-001" in active_ids
        assert "std-inactive-001" not in active_ids

    def test_seed_defaults_creates_standards(self, db, standards):
        """seed_defaults() creates default standards in DB."""
        count = standards.seed_defaults()
        assert count > 0

        rows = _raw_query(db, "SELECT * FROM quality_standards")
        assert len(rows) == count

        # All seeded standards are active
        for row in rows:
            assert row["active"] == 1

    def test_seed_defaults_idempotent(self, standards):
        """Calling seed_defaults() twice does not duplicate entries."""
        first_count = standards.seed_defaults()
        second_count = standards.seed_defaults()
        assert second_count == 0  # No new standards created

        active = standards.get_active()
        assert len(active) == first_count

    def test_deactivate_then_seed_does_not_reactivate(self, db, standards):
        """seed_defaults() does not re-create deactivated standards."""
        standards.seed_defaults()
        all_ids = [s["id"] for s in standards.get_active()]
        assert len(all_ids) > 0

        # Deactivate the first standard
        standards.deactivate(all_ids[0])

        # Re-seed — should not add new rows for existing IDs
        re_count = standards.seed_defaults()
        assert re_count == 0

        # Verify the deactivated standard remains inactive
        row = _raw_query_one(
            db, "SELECT active FROM quality_standards WHERE id = ?", (all_ids[0],)
        )
        assert row["active"] == 0

    def test_get_context_payload_reflects_writes(self, standards):
        """get_context_payload() returns JSON only for active critical/important standards."""
        standards.create(
            standard_id="std-ctx-001", category="security",
            rule="Sanitize all inputs", severity="critical",
        )
        standards.create(
            standard_id="std-ctx-002", category="style",
            rule="Use consistent naming", severity="low",
        )

        payload = json.loads(standards.get_context_payload())
        payload_ids = [p["rule"] for p in payload]
        assert "Sanitize all inputs" in payload_ids
        assert "Use consistent naming" not in payload_ids  # Low severity excluded


# ---------------------------------------------------------------------------
# 2. ContextEngine Write Operations
# ---------------------------------------------------------------------------


class TestContextEngineWrites:
    """Verify ContextEngine record_request and evaluate_relevance writes."""

    def test_record_request_persists_to_db(self, db, context_engine, sessions, doc_store):
        """record_request() inserts a row into context_requests table."""
        session = sessions.start(project="greymatter")
        doc = doc_store.create(
            doc_type="design", title="Test Doc",
            content="Content for context test", project="greymatter",
        )

        context_engine.record_request(
            session_id=session["id"],
            query="architecture overview",
            doc_ids=[doc["id"]],
            tokens=500,
        )

        row = _raw_query_one(
            db, "SELECT * FROM context_requests WHERE session_id = ?",
            (session["id"],),
        )
        assert row is not None
        assert row["query"] == "architecture overview"
        assert json.loads(row["documents_served"]) == [doc["id"]]
        assert row["tokens_served"] == 500
        assert row["relevance_score"] is None  # Not yet evaluated
        assert row["requested_at"] is not None

    def test_evaluate_relevance_updates_score(self, db, context_engine, sessions, doc_store):
        """evaluate_relevance() updates the relevance_score for a recorded request."""
        session = sessions.start(project="greymatter")
        doc = doc_store.create(
            doc_type="design", title="Eval Doc",
            content="Content", project="greymatter",
        )

        context_engine.record_request(
            session_id=session["id"],
            query="test query",
            doc_ids=[doc["id"]],
            tokens=100,
        )

        # Get the auto-incremented ID
        row = _raw_query_one(
            db, "SELECT id FROM context_requests WHERE session_id = ?",
            (session["id"],),
        )
        request_id = row["id"]

        context_engine.evaluate_relevance(request_id, score=0.85)

        updated = _raw_query_one(
            db, "SELECT relevance_score FROM context_requests WHERE id = ?",
            (request_id,),
        )
        assert updated["relevance_score"] == pytest.approx(0.85)

    def test_multiple_requests_same_session(self, db, context_engine, sessions, doc_store):
        """Multiple record_request() calls for the same session accumulate."""
        session = sessions.start(project="greymatter")
        doc = doc_store.create(
            doc_type="research", title="Multi-req Doc",
            content="Content", project="greymatter",
        )

        for i in range(3):
            context_engine.record_request(
                session_id=session["id"],
                query=f"query_{i}",
                doc_ids=[doc["id"]],
                tokens=100 * (i + 1),
            )

        rows = _raw_query(
            db, "SELECT * FROM context_requests WHERE session_id = ? ORDER BY id",
            (session["id"],),
        )
        assert len(rows) == 3
        assert rows[0]["tokens_served"] == 100
        assert rows[1]["tokens_served"] == 200
        assert rows[2]["tokens_served"] == 300

    def test_record_request_with_empty_doc_ids(self, db, context_engine, sessions):
        """record_request() with empty doc list persists correctly."""
        session = sessions.start(project="greymatter")
        context_engine.record_request(
            session_id=session["id"],
            query="empty context",
            doc_ids=[],
            tokens=0,
        )

        row = _raw_query_one(
            db, "SELECT * FROM context_requests WHERE session_id = ?",
            (session["id"],),
        )
        assert row is not None
        assert json.loads(row["documents_served"]) == []
        assert row["tokens_served"] == 0


# ---------------------------------------------------------------------------
# 3. PolicyEngine In-Memory Write Operations
# ---------------------------------------------------------------------------


class TestPolicyEngineWrites:
    """Verify PolicyEngine add_rule, remove_rule, and evaluation state."""

    def test_add_rule_inserts_and_sorts_by_priority(self):
        """add_rule() inserts a rule and maintains priority sort order."""
        engine = PolicyEngine()
        rule_low = PolicyRule(
            rule_id="r-low", description="Low priority",
            outcome=PolicyOutcome.ALLOW, priority=100,
        )
        rule_high = PolicyRule(
            rule_id="r-high", description="High priority",
            outcome=PolicyOutcome.REFUSE, priority=10,
        )

        engine.add_rule(rule_low)
        engine.add_rule(rule_high)

        assert len(engine.rules) == 2
        assert engine.rules[0].rule_id == "r-high"  # Higher priority (lower number) first
        assert engine.rules[1].rule_id == "r-low"

    def test_remove_rule_by_id(self):
        """remove_rule() removes the specified rule and returns True."""
        engine = PolicyEngine()
        engine.add_rule(PolicyRule(
            rule_id="r-remove", description="To remove",
            outcome=PolicyOutcome.ALLOW, priority=50,
        ))
        engine.add_rule(PolicyRule(
            rule_id="r-keep", description="To keep",
            outcome=PolicyOutcome.ALLOW, priority=100,
        ))

        result = engine.remove_rule("r-remove")
        assert result is True
        assert len(engine.rules) == 1
        assert engine.rules[0].rule_id == "r-keep"

    def test_remove_nonexistent_rule_returns_false(self):
        """remove_rule() returns False when the rule_id doesn't exist."""
        engine = PolicyEngine()
        assert engine.remove_rule("r-ghost") is False

    def test_add_rule_affects_evaluation(self):
        """Adding a rule changes evaluation outcome for matching context."""
        engine = PolicyEngine()
        ctx = EvalContext(tool="gm_observe", action="create", content="secret password")

        # Default: allow
        decision_before = engine.evaluate(ctx)
        assert decision_before.outcome == PolicyOutcome.ALLOW
        assert decision_before.rule_id == "_default"

        # Add a refuse rule matching "secret"
        engine.add_rule(PolicyRule(
            rule_id="r-secret", description="Block secrets",
            outcome=PolicyOutcome.REFUSE, priority=10,
            conditions=[Condition(field="content", operator="contains", value="secret")],
            reason="Content contains sensitive data",
        ))

        decision_after = engine.evaluate(ctx)
        assert decision_after.outcome == PolicyOutcome.REFUSE
        assert decision_after.rule_id == "r-secret"
        assert decision_after.reason == "Content contains sensitive data"

    def test_remove_rule_reverts_evaluation(self):
        """Removing a rule restores default evaluation behavior."""
        engine = PolicyEngine(rules=[
            PolicyRule(
                rule_id="r-block", description="Block all",
                outcome=PolicyOutcome.REFUSE, priority=1,
            ),
        ])
        ctx = EvalContext(tool="gm_knowledge", action="create")

        assert engine.evaluate(ctx).outcome == PolicyOutcome.REFUSE

        engine.remove_rule("r-block")
        assert engine.evaluate(ctx).outcome == PolicyOutcome.ALLOW

    def test_add_shadow_rule_does_not_affect_decision(self):
        """Shadow rules are evaluated but don't change the primary decision."""
        engine = PolicyEngine()
        engine.add_rule(PolicyRule(
            rule_id="r-shadow", description="Shadow monitor",
            outcome=PolicyOutcome.REFUSE, priority=1,
            shadow=True,
        ))

        ctx = EvalContext(tool="gm_knowledge")
        decision = engine.evaluate(ctx)
        assert decision.outcome == PolicyOutcome.ALLOW  # Shadow rule doesn't block

    def test_multiple_rules_priority_ordering(self):
        """Multiple rules evaluated in priority order; first non-shadow match wins."""
        engine = PolicyEngine()
        engine.add_rule(PolicyRule(
            rule_id="r-escalate", description="Escalate sensitive",
            outcome=PolicyOutcome.ESCALATE, priority=50,
            conditions=[Condition(field="content", operator="contains", value="PII")],
        ))
        engine.add_rule(PolicyRule(
            rule_id="r-allow", description="Allow all",
            outcome=PolicyOutcome.ALLOW, priority=100,
        ))
        engine.add_rule(PolicyRule(
            rule_id="r-refuse", description="Refuse dangerous",
            outcome=PolicyOutcome.REFUSE, priority=10,
            conditions=[Condition(field="content", operator="contains", value="DROP TABLE")],
        ))

        # Content with PII but no DROP TABLE → escalate (priority 50)
        ctx_pii = EvalContext(content="User PII data here")
        assert engine.evaluate(ctx_pii).outcome == PolicyOutcome.ESCALATE

        # Content with DROP TABLE → refuse (priority 10, checked first)
        ctx_danger = EvalContext(content="DROP TABLE users")
        assert engine.evaluate(ctx_danger).outcome == PolicyOutcome.REFUSE

        # Benign content → allow (priority 100, catch-all)
        ctx_safe = EvalContext(content="Normal content")
        assert engine.evaluate(ctx_safe).outcome == PolicyOutcome.ALLOW


# ---------------------------------------------------------------------------
# 4. ProactiveRecall record_review Writes
# ---------------------------------------------------------------------------


class TestProactiveRecallWrites:
    """Verify ProactiveRecall.record_review writes across all source tables."""

    def test_review_knowledge_active_bumps_applied_count(self, recall_db, recall):
        """Active review of knowledge entry increments applied_count."""
        entry_id = "kn-recall-001"
        _seed_knowledge_in_recall_db(recall_db, entry_id, "Active Review Test")

        result = recall.record_review(entry_id, source_table="knowledge_entries")
        assert result is True

        conn = sqlite3.connect(recall_db)
        conn.row_factory = sqlite3.Row
        row = dict(conn.execute(
            "SELECT applied_count, updated_at FROM knowledge_entries WHERE id = ?",
            (entry_id,),
        ).fetchone())
        conn.close()

        assert row["applied_count"] == 1
        assert row["updated_at"] is not None

    def test_review_knowledge_passive_resets_clock_only(self, recall_db, recall):
        """Passive review (low factor) resets decay clock without bumping count."""
        entry_id = "kn-recall-002"
        _seed_knowledge_in_recall_db(recall_db, entry_id, "Passive Review Test")

        result = recall.record_review(
            entry_id, source_table="knowledge_entries",
            stability_factor=1.5,  # Below CONFIRM_FACTOR (2.5) → passive
        )
        assert result is True

        conn = sqlite3.connect(recall_db)
        conn.row_factory = sqlite3.Row
        row = dict(conn.execute(
            "SELECT applied_count FROM knowledge_entries WHERE id = ?",
            (entry_id,),
        ).fetchone())
        conn.close()

        assert row["applied_count"] == 0  # Passive review doesn't bump count

    def test_review_observation_increases_stability(self, recall_db, recall):
        """Active review of observation increases stability and confidence."""
        obs_id = "obs-recall-001"
        _seed_observation_in_recall_db(recall_db, obs_id)

        result = recall.record_review(obs_id, source_table="observations")
        assert result is True

        conn = sqlite3.connect(recall_db)
        conn.row_factory = sqlite3.Row
        row = dict(conn.execute(
            "SELECT stability_days, confidence, review_count, review_source FROM observations WHERE id = ?",
            (obs_id,),
        ).fetchone())
        conn.close()

        assert row["stability_days"] > 45  # Default was 45, should increase
        assert row["confidence"] > 0.5     # Default was 0.5, should increase
        assert row["review_count"] == 1
        assert row["review_source"] == "manual"  # Default review_source

    def test_review_observation_with_custom_source(self, recall_db, recall):
        """record_review with custom review_source tags correctly."""
        obs_id = "obs-recall-002"
        _seed_observation_in_recall_db(recall_db, obs_id)

        recall.record_review(
            obs_id, source_table="observations",
            review_source="session_recall",
        )

        conn = sqlite3.connect(recall_db)
        conn.row_factory = sqlite3.Row
        row = dict(conn.execute(
            "SELECT review_source FROM observations WHERE id = ?", (obs_id,),
        ).fetchone())
        conn.close()

        assert row["review_source"] == "session_recall"

    def test_review_pattern_active_bumps_confirmations(self, recall_db, recall):
        """Active review of pattern increments confirmations."""
        pat_id = "pat-recall-001"
        _seed_pattern_in_recall_db(recall_db, pat_id)

        result = recall.record_review(pat_id, source_table="patterns")
        assert result is True

        conn = sqlite3.connect(recall_db)
        conn.row_factory = sqlite3.Row
        row = dict(conn.execute(
            "SELECT confirmations, last_applied_at, updated_at FROM patterns WHERE id = ?",
            (pat_id,),
        ).fetchone())
        conn.close()

        assert row["confirmations"] == 1
        assert row["last_applied_at"] is not None
        assert row["updated_at"] is not None

    def test_review_auto_detects_source_table(self, recall_db, recall):
        """record_review auto-detects source table from ID prefix."""
        entry_id = "kn-autodetect-001"
        _seed_knowledge_in_recall_db(recall_db, entry_id, "Auto-detect Test")

        # No source_table arg — should auto-detect from "kn-" prefix
        result = recall.record_review(entry_id)
        assert result is True

    def test_review_nonexistent_observation_returns_false(self, recall_db, recall):
        """review of non-existent observation returns False."""
        result = recall.record_review("obs-ghost-999", source_table="observations")
        assert result is False

    def test_review_unknown_prefix_returns_false(self, recall_db, recall):
        """Auto-detection fails gracefully for unknown ID prefixes."""
        result = recall.record_review("zzz-unknown-001")
        assert result is False

    def test_multiple_reviews_compound_stability(self, recall_db, recall):
        """Multiple reviews compound stability increases (spaced repetition)."""
        obs_id = "obs-compound-001"
        _seed_observation_in_recall_db(recall_db, obs_id)

        recall.record_review(obs_id, source_table="observations")
        recall.record_review(obs_id, source_table="observations")

        conn = sqlite3.connect(recall_db)
        conn.row_factory = sqlite3.Row
        row = dict(conn.execute(
            "SELECT stability_days, review_count FROM observations WHERE id = ?",
            (obs_id,),
        ).fetchone())
        conn.close()

        # After 2 reviews: stability should be 45 * 2.5 * 2.5 = 281.25
        assert row["stability_days"] > 200
        assert row["review_count"] == 2


# ---------------------------------------------------------------------------
# 5. Work Item update_review Edge Cases
# ---------------------------------------------------------------------------


class TestWorkItemReviewEdgeCases:
    """Verify update_review edge cases and idempotency."""

    def test_update_review_approved_persists_notes(self, db, work_items):
        """update_review with 'approved' status and notes persists both."""
        w = work_items.create(title="Review Edge Case", project="greymatter")
        work_items.update_status(w["id"], "pending_review")

        result = work_items.update_review(
            w["id"],
            status="approved",
            review_notes="LGTM. Tests pass, coverage adequate.",
        )
        assert result["status"] == "approved"
        assert result["review_notes"] == "LGTM. Tests pass, coverage adequate."

        row = _raw_query_one(db, "SELECT * FROM work_items WHERE id = ?", (w["id"],))
        assert row["status"] == "approved"
        assert row["review_notes"] == "LGTM. Tests pass, coverage adequate."

    def test_update_review_needs_revision_then_approved(self, db, work_items):
        """Work item can go from needs_revision back to approved."""
        w = work_items.create(title="Revision Flow")
        work_items.update_status(w["id"], "pending_review")

        work_items.update_review(w["id"], "needs_revision", review_notes="Fix lint errors")
        row = _raw_query_one(db, "SELECT status, review_notes FROM work_items WHERE id = ?", (w["id"],))
        assert row["status"] == "needs_revision"
        assert row["review_notes"] == "Fix lint errors"

        work_items.update_review(w["id"], "approved", review_notes="All issues resolved")
        row = _raw_query_one(db, "SELECT status, review_notes FROM work_items WHERE id = ?", (w["id"],))
        assert row["status"] == "approved"
        assert row["review_notes"] == "All issues resolved"

    def test_update_review_none_notes(self, db, work_items):
        """update_review with no notes works correctly."""
        w = work_items.create(title="No Notes Review")
        work_items.update_status(w["id"], "pending_review")

        result = work_items.update_review(w["id"], "approved")
        assert result["status"] == "approved"

    def test_update_review_nonexistent_returns_none(self, work_items):
        """update_review on non-existent work item returns None."""
        result = work_items.update_review("wi-nonexistent", "approved")
        assert result is None

    def test_multiple_consecutive_reviews(self, db, work_items):
        """Multiple consecutive update_review calls preserve only the latest notes."""
        w = work_items.create(title="Multi Review")
        work_items.update_status(w["id"], "pending_review")

        work_items.update_review(w["id"], "needs_revision", review_notes="Round 1")
        work_items.update_review(w["id"], "needs_revision", review_notes="Round 2")
        work_items.update_review(w["id"], "approved", review_notes="Final approval")

        row = _raw_query_one(db, "SELECT status, review_notes FROM work_items WHERE id = ?", (w["id"],))
        assert row["status"] == "approved"
        assert row["review_notes"] == "Final approval"


# ---------------------------------------------------------------------------
# 6. Cross-Module: Standards → Context → Quality Pipeline
# ---------------------------------------------------------------------------


class TestCrossModuleStandardsContextPipeline:
    """Verify write consistency across standards, context, and quality modules."""

    def test_standards_seed_feeds_context_engine(
        self, standards, context_engine, sessions, doc_store, patterns,
    ):
        """Seeded standards appear in context_engine.build_session_context output."""
        standards.seed_defaults()
        session = sessions.start(project="greymatter")
        doc_store.create(
            doc_type="design", title="System Architecture",
            content="GreyMatter uses SQLite with WAL mode.",
            project="greymatter",
        )

        ctx = context_engine.build_session_context(project="greymatter")
        assert ctx["quality_standards"] is not None
        assert len(ctx["quality_standards"]) > 0  # Standards payload is non-empty

        sessions.end(session["id"], summary="Context test")

    def test_deactivated_standard_removed_from_context(
        self, standards, context_engine, sessions, doc_store, patterns,
    ):
        """Deactivating a standard removes it from context payload."""
        standards.seed_defaults()
        active_before = standards.get_active(severity_filter=["critical", "important"])
        assert len(active_before) > 0

        # Deactivate all active standards
        for s in active_before:
            standards.deactivate(s["id"])

        payload = json.loads(standards.get_context_payload())
        assert len(payload) == 0

    def test_context_request_tracking_with_quality_check(
        self, db, context_engine, quality, knowledge, sessions, doc_store,
    ):
        """Full pipeline: create knowledge → context request → quality check."""
        session = sessions.start(project="greymatter")

        knowledge.create(
            entry_type="expertise", title="WAL concurrency",
            content="WAL allows concurrent reads during writes",
            domain="database",
        )

        doc = doc_store.create(
            doc_type="research", title="SQLite Research",
            content="Research on WAL mode performance characteristics",
            project="greymatter",
        )

        context_engine.record_request(
            session_id=session["id"],
            query="WAL mode",
            doc_ids=[doc["id"]],
            tokens=250,
        )

        # Verify context request persisted
        request = _raw_query_one(
            db, "SELECT * FROM context_requests WHERE session_id = ?",
            (session["id"],),
        )
        assert request is not None
        assert request["tokens_served"] == 250

        # Quality check works alongside context tracking
        report = quality.full_check()
        assert report.total_entries >= 1

        sessions.end(session["id"], summary="Context tracking pipeline test")

    def test_standards_create_deactivate_context_roundtrip(
        self, db, standards, context_engine, sessions, doc_store, patterns,
    ):
        """Create → use in context → deactivate → verify removed from context."""
        standards.create(
            standard_id="std-roundtrip-001",
            category="testing",
            rule="All tests must be deterministic",
            severity="critical",
        )

        # Verify it appears in context payload
        payload = json.loads(standards.get_context_payload())
        rules = [p["rule"] for p in payload]
        assert "All tests must be deterministic" in rules

        # Deactivate
        standards.deactivate("std-roundtrip-001")

        # Verify removal from context
        payload_after = json.loads(standards.get_context_payload())
        rules_after = [p["rule"] for p in payload_after]
        assert "All tests must be deterministic" not in rules_after


# ---------------------------------------------------------------------------
# 7. Quality Engine Extended Write Validation
# ---------------------------------------------------------------------------


class TestQualityEngineExtendedWrites:
    """Verify quality engine merge edge cases and batch operations."""

    def test_merge_preserves_applied_count(self, db, knowledge, quality):
        """merge_duplicates preserves the keep entry's applied_count."""
        keep = knowledge.create(
            entry_type="expertise", title="Keep Entry",
            content="Original content",
        )
        knowledge.record_application(keep["id"])
        knowledge.record_application(keep["id"])

        remove = knowledge.create(
            entry_type="expertise", title="Remove Entry",
            content="Duplicate content",
        )

        quality.merge_duplicates(keep["id"], remove["id"])

        row = _raw_query_one(
            db, "SELECT applied_count FROM knowledge_entries WHERE id = ?",
            (keep["id"],),
        )
        assert row["applied_count"] == 2  # Preserved from before merge

    def test_sequential_merges_accumulate_content(self, db, knowledge, quality):
        """Multiple sequential merges into the same keep entry accumulate content."""
        keep = knowledge.create(
            entry_type="expertise", title="Accumulator",
            content="Base content.",
        )
        dup1 = knowledge.create(
            entry_type="expertise", title="Duplicate 1",
            content="Extra info 1.",
        )
        dup2 = knowledge.create(
            entry_type="expertise", title="Duplicate 2",
            content="Extra info 2.",
        )

        quality.merge_duplicates(keep["id"], dup1["id"])
        quality.merge_duplicates(keep["id"], dup2["id"])

        row = _raw_query_one(
            db, "SELECT content FROM knowledge_entries WHERE id = ?",
            (keep["id"],),
        )
        assert "Base content" in row["content"]
        assert "Extra info 1" in row["content"]
        assert "Extra info 2" in row["content"]

        # Both duplicates pruned
        for dup_id in [dup1["id"], dup2["id"]]:
            dup_row = _raw_query_one(
                db, "SELECT pruned_at FROM knowledge_entries WHERE id = ?",
                (dup_id,),
            )
            assert dup_row["pruned_at"] is not None

    def test_merge_with_stale_entry(self, db, knowledge, quality):
        """Merging a stale entry into a fresh one preserves freshness."""
        fresh = knowledge.create(
            entry_type="expertise", title="Fresh",
            content="Recent knowledge",
        )
        stale = knowledge.create(
            entry_type="expertise", title="Stale",
            content="Old knowledge",
        )
        # Flag stale entry
        db.execute(
            "UPDATE knowledge_entries SET stale_flagged_at = datetime('now') WHERE id = ?",
            (stale["id"],),
        )

        quality.merge_duplicates(fresh["id"], stale["id"])

        row = _raw_query_one(
            db, "SELECT stale_flagged_at FROM knowledge_entries WHERE id = ?",
            (fresh["id"],),
        )
        assert row["stale_flagged_at"] is None  # Fresh entry stays fresh
