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

Validates write operations and cross-module consistency for modules
not covered by existing write integration tests:

- **BenchmarkManager analytics accuracy**: defect_density, first_pass_quality,
  time_to_correct, and summary aggregate queries after sequences of writes
  (start_run, complete_run, score_run, record_issue, resolve_issue).
- **ChannelAuditLogger write-replay roundtrip**: log_sync, log_share,
  log_retract, log_review -> get_events -> replay -> format_event consistency.
- **SecurityAudit write-read roundtrip**: log_detection, log_policy_decision ->
  get_recent_events -> get_stats persistence via raw sqlite3 connections.
- **Cross-module pipeline**: Benchmarks -> Standards -> Quality scoring with
  analytics verification across all three tables.

Architecture:
    BenchmarkManager and cross-module tests use the in-memory ``db`` fixture.
    ChannelAuditLogger tests use ``audit_db`` which adds the channel_audit_log
    schema on top of core schema.
    SecurityAudit tests use a file-backed ``security_db_path`` fixture
    because security_audit.py opens its own raw sqlite3 connections.

Run with::

    pytest core/tests/test_uncovered_write_integration.py -v
"""

import json
import os
import sqlite3
from typing import Any
from unittest.mock import patch

import pytest

from greymatter_core.benchmarks import BenchmarkManager
from greymatter_core.channel_audit import (
    AuditEvent,
    AuditOutcome,
    AuditSeverity,
    CHANNEL_AUDIT_SCHEMA,
    ChannelAuditLogger,
    ChannelOperation,
    format_event,
    format_events,
    format_replay_report,
)
from greymatter_core.db import SQLiteDatabase
from greymatter_core.knowledge import KnowledgeStore
from greymatter_core.quality import QualityEngine
from greymatter_core.security_audit import (
    _content_hash,
    get_recent_events,
    get_stats,
    log_detection,
    log_policy_decision,
)
from greymatter_core.standards import StandardsManager
from greymatter_core.work_items import WorkItemManager


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


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


@pytest.fixture
def benchmarks(db):
    return BenchmarkManager(db)


@pytest.fixture
def standards(db):
    return StandardsManager(db)


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


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


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


@pytest.fixture
def audit_db():
    """In-memory database with core + channel_audit_log schema."""
    database = SQLiteDatabase(":memory:")
    database._conn.executescript(CHANNEL_AUDIT_SCHEMA)
    database._conn.commit()
    yield database
    database.close()


@pytest.fixture
def audit_logger(audit_db):
    return ChannelAuditLogger(audit_db)


# DDL for _security_audit table — mirrors the production schema in
# security_audit._ensure_table().  Pre-creating the table in the test
# fixture ensures a clean, isolated schema for each test without
# relying on the fail-silent _write_event() to bootstrap it.
_SECURITY_AUDIT_DDL = """
CREATE TABLE IF NOT EXISTS _security_audit (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    event_type TEXT NOT NULL,
    hook_phase TEXT NOT NULL,
    tool_name TEXT NOT NULL DEFAULT '',
    severity TEXT NOT NULL DEFAULT 'info',
    pattern_name TEXT DEFAULT NULL,
    confidence REAL DEFAULT NULL,
    outcome TEXT NOT NULL DEFAULT 'allow',
    rule_id TEXT DEFAULT NULL,
    reason TEXT DEFAULT NULL,
    content_hash TEXT DEFAULT NULL,
    session_id TEXT DEFAULT NULL,
    node_id TEXT DEFAULT NULL,
    created_at TEXT DEFAULT (datetime('now'))
)
"""


@pytest.fixture
def security_db_path(tmp_path):
    """File-backed DB path for security_audit tests.

    security_audit.py opens its own raw sqlite3 connections,
    so we need a real file, not :memory:.
    """
    db_path = tmp_path / "security_test.db"
    conn = sqlite3.connect(str(db_path))
    conn.execute(_SECURITY_AUDIT_DDL)
    conn.commit()
    conn.close()
    return str(db_path)


# ---------------------------------------------------------------------------
# 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_benchmark_runs(benchmarks: BenchmarkManager) -> list[dict[str, Any]]:
    """Create a set of benchmark runs across different approaches for analytics tests."""
    runs = []

    # GreyMatter approach: 2 runs
    r1 = benchmarks.start_run("greymatter", "Feature A")
    benchmarks.complete_run(r1["id"], lines=500, tests=25, passing=25, first_pass=True)
    runs.append(r1)

    r2 = benchmarks.start_run("greymatter", "Feature B")
    benchmarks.complete_run(r2["id"], lines=300, tests=10, passing=9, first_pass=False)
    runs.append(r2)

    # Subagent approach: 1 run
    r3 = benchmarks.start_run("subagent", "Feature C")
    benchmarks.complete_run(r3["id"], lines=400, tests=20, passing=18, first_pass=False)
    runs.append(r3)

    # Raw Claude approach: 1 run (no issues = zero defect density)
    r4 = benchmarks.start_run("raw_claude", "Feature D")
    benchmarks.complete_run(r4["id"], lines=200, tests=8, passing=8, first_pass=True)
    runs.append(r4)

    return runs


# ---------------------------------------------------------------------------
# 1. BenchmarkManager Analytics Accuracy After Writes
# ---------------------------------------------------------------------------


class TestBenchmarkAnalyticsAccuracy:
    """Verify that analytics queries return correct aggregates after write operations."""

    def test_defect_density_reflects_issues(self, db, benchmarks):
        """defect_density() correctly calculates defects per KLOC after issue writes."""
        runs = _seed_benchmark_runs(benchmarks)

        # Add issues: 2 issues to greymatter run1 (500 lines), 3 to subagent (400 lines)
        benchmarks.record_issue(runs[0]["id"], "critical", "security", "SQL injection", "review")
        benchmarks.record_issue(runs[0]["id"], "minor", "style", "Naming", "review")
        benchmarks.record_issue(runs[2]["id"], "critical", "logic", "Off-by-one", "review")
        benchmarks.record_issue(runs[2]["id"], "important", "testing", "Missing edge case", "review")
        benchmarks.record_issue(runs[2]["id"], "minor", "style", "Long lines", "review")

        results = benchmarks.defect_density()
        by_approach = {r["approach"]: r["defects_per_kloc"] for r in results}

        # greymatter: 2 issues across (500 + 300) = 800 lines -> 2.5 per KLOC
        assert by_approach["greymatter"] == pytest.approx(2.5, abs=0.01)

        # subagent: 3 issues across 400 lines -> 7.5 per KLOC
        assert by_approach["subagent"] == pytest.approx(7.5, abs=0.01)

        # raw_claude: 0 issues across 200 lines -> 0 per KLOC
        assert by_approach["raw_claude"] == pytest.approx(0.0, abs=0.01)

    def test_defect_density_filtered_by_approach(self, benchmarks):
        """defect_density(approach=...) filters to a single approach."""
        runs = _seed_benchmark_runs(benchmarks)
        benchmarks.record_issue(runs[0]["id"], "minor", "style", "Test", "review")

        results = benchmarks.defect_density(approach="greymatter")
        assert len(results) == 1
        assert results[0]["approach"] == "greymatter"

    def test_first_pass_quality_accuracy(self, benchmarks, standards):
        """first_pass_quality() reflects score_run writes as percentages."""
        standards.create(
            standard_id="std-fpq-001", category="testing",
            rule="All writes tested", severity="critical",
        )

        runs = _seed_benchmark_runs(benchmarks)

        # Score greymatter runs: both pass
        benchmarks.score_run(runs[0]["id"], "std-fpq-001", passed=True, found_by="review")
        benchmarks.score_run(runs[1]["id"], "std-fpq-001", passed=True, found_by="review")

        # Score subagent run: fail
        benchmarks.score_run(runs[2]["id"], "std-fpq-001", passed=False, found_by="review")

        # Score raw_claude: pass
        benchmarks.score_run(runs[3]["id"], "std-fpq-001", passed=True, found_by="review")

        results = benchmarks.first_pass_quality()
        by_approach = {r["approach"]: r["first_pass_pct"] for r in results}

        # greymatter: 2/2 passed -> 100%
        assert by_approach["greymatter"] == pytest.approx(100.0, abs=0.01)

        # subagent: 0/1 passed -> 0%
        assert by_approach["subagent"] == pytest.approx(0.0, abs=0.01)

        # raw_claude: 1/1 passed -> 100%
        assert by_approach["raw_claude"] == pytest.approx(100.0, abs=0.01)

    def test_time_to_correct_accuracy(self, benchmarks):
        """time_to_correct() averages fix times for resolved issues."""
        runs = _seed_benchmark_runs(benchmarks)

        # greymatter: 2 issues, fixed in 5 and 15 minutes -> avg 10
        id1 = benchmarks.record_issue(runs[0]["id"], "critical", "security", "Bug 1", "review")
        id2 = benchmarks.record_issue(runs[1]["id"], "minor", "style", "Bug 2", "review")
        benchmarks.resolve_issue(id1, fix_minutes=5)
        benchmarks.resolve_issue(id2, fix_minutes=15)

        # subagent: 1 issue, fixed in 30 minutes
        id3 = benchmarks.record_issue(runs[2]["id"], "important", "logic", "Bug 3", "review")
        benchmarks.resolve_issue(id3, fix_minutes=30)

        results = benchmarks.time_to_correct()
        by_approach = {r["approach"]: r["avg_fix_minutes"] for r in results}

        assert by_approach["greymatter"] == pytest.approx(10.0, abs=0.01)
        assert by_approach["subagent"] == pytest.approx(30.0, abs=0.01)

        # raw_claude: no resolved issues -> should not appear
        assert "raw_claude" not in by_approach

    def test_time_to_correct_excludes_unresolved(self, benchmarks):
        """time_to_correct() only counts resolved issues (fixed_at IS NOT NULL)."""
        runs = _seed_benchmark_runs(benchmarks)

        id1 = benchmarks.record_issue(runs[0]["id"], "critical", "security", "Resolved", "review")
        benchmarks.record_issue(runs[0]["id"], "minor", "style", "Unresolved", "review")
        benchmarks.resolve_issue(id1, fix_minutes=20)

        results = benchmarks.time_to_correct(approach="greymatter")
        assert len(results) == 1
        assert results[0]["avg_fix_minutes"] == pytest.approx(20.0, abs=0.01)

    def test_summary_reflects_all_completed_runs(self, db, benchmarks):
        """summary() includes all completed runs grouped by approach."""
        _seed_benchmark_runs(benchmarks)

        results = benchmarks.summary()
        approaches = {r["approach"] for r in results}
        assert approaches == {"greymatter", "subagent", "raw_claude"}

    def test_incomplete_run_excluded_from_defect_density(self, benchmarks):
        """Incomplete runs (no completed_at) are excluded from defect_density()."""
        incomplete = benchmarks.start_run("greymatter", "WIP Feature")
        benchmarks.record_issue(incomplete["id"], "critical", "security", "Bug", "review")

        results = benchmarks.defect_density(approach="greymatter")
        # No completed runs -> empty results or zero
        assert len(results) == 0 or all(r["defects_per_kloc"] is None for r in results)

    def test_invalid_approach_raises(self, benchmarks):
        """start_run() with invalid approach raises ValueError."""
        with pytest.raises(ValueError, match="Invalid approach"):
            benchmarks.start_run("invalid_approach", "Bad Run")

    def test_invalid_severity_raises(self, benchmarks):
        """record_issue() with invalid severity raises ValueError."""
        run = benchmarks.start_run("greymatter", "Test")
        with pytest.raises(ValueError, match="Invalid severity"):
            benchmarks.record_issue(run["id"], "blocker", "security", "Bad severity", "review")


# ---------------------------------------------------------------------------
# 2. ChannelAuditLogger Write-Replay Roundtrip
# ---------------------------------------------------------------------------


class TestChannelAuditWriteReplay:
    """Verify ChannelAuditLogger write -> query -> replay roundtrip."""

    def test_log_sync_persists_all_fields(self, audit_db, audit_logger):
        """log_sync() writes all fields to channel_audit_log table."""
        event_id = audit_logger.log_sync(
            "ch-team",
            "push",
            items_affected=42,
            outcome="success",
            target_node="node-2",
            data_scope="observations,knowledge",
            detail="Synced 42 items push",
            payload={"version": "1.2.0", "conflicts": 0},
            actor="dispatch-1",
            actor_type="agent",
        )
        assert event_id is not None

        row = _raw_query_one(
            audit_db,
            "SELECT * FROM channel_audit_log WHERE id = ?",
            (event_id,),
        )
        assert row is not None
        assert row["channel_id"] == "ch-team"
        assert row["operation"] == "sync"
        assert row["severity"] == "info"
        assert row["outcome"] == "success"
        assert row["direction"] == "push"
        assert row["target_node"] == "node-2"
        assert row["data_scope"] == "observations,knowledge"
        assert row["items_affected"] == 42
        assert row["detail"] == "Synced 42 items push"
        assert row["actor"] == "dispatch-1"
        assert row["actor_type"] == "agent"
        assert row["created_at"] is not None

        payload = json.loads(row["payload"])
        assert payload["version"] == "1.2.0"
        assert payload["conflicts"] == 0

    def test_log_share_persists_correctly(self, audit_db, audit_logger):
        """log_share() writes with outbound direction and correct severity."""
        event_id = audit_logger.log_share(
            "ch-public",
            target_node="node-3",
            items_affected=10,
            data_scope="patterns",
            actor="user-1",
            actor_type="user",
        )
        assert event_id is not None

        row = _raw_query_one(
            audit_db,
            "SELECT * FROM channel_audit_log WHERE id = ?",
            (event_id,),
        )
        assert row["operation"] == "share"
        assert row["direction"] == "outbound"
        assert row["target_node"] == "node-3"
        assert row["severity"] == "info"  # success -> info

    def test_log_share_denied_severity_high(self, audit_db, audit_logger):
        """log_share() with denied outcome sets severity to high."""
        event_id = audit_logger.log_share(
            "ch-personal",
            target_node="node-external",
            outcome="denied",
            detail="Personal channel cannot be shared",
        )

        row = _raw_query_one(
            audit_db,
            "SELECT * FROM channel_audit_log WHERE id = ?",
            (event_id,),
        )
        assert row["severity"] == "high"
        assert row["outcome"] == "denied"

    def test_log_retract_always_high_severity(self, audit_db, audit_logger):
        """log_retract() sets severity=high (critical on failure)."""
        event_id = audit_logger.log_retract(
            "ch-team",
            items_affected=5,
            target_node="node-2",
            actor="admin",
            actor_type="user",
        )

        row = _raw_query_one(
            audit_db,
            "SELECT * FROM channel_audit_log WHERE id = ?",
            (event_id,),
        )
        assert row["severity"] == "high"
        assert row["direction"] == "retract"

    def test_log_retract_failed_critical_severity(self, audit_db, audit_logger):
        """log_retract() with failed outcome escalates to critical."""
        event_id = audit_logger.log_retract(
            "ch-team",
            outcome="failed",
            detail="Network timeout during retract",
        )

        row = _raw_query_one(
            audit_db,
            "SELECT * FROM channel_audit_log WHERE id = ?",
            (event_id,),
        )
        assert row["severity"] == "critical"
        assert row["outcome"] == "failed"

    def test_log_review_by_agent_elevated_severity(self, audit_db, audit_logger):
        """log_review() by AI actor elevates severity to at least warn."""
        event_id = audit_logger.log_review(
            "ch-team",
            outcome="success",
            actor="dispatch-agent-1",
            actor_type="agent",
            detail="Automated review: all items verified",
        )

        row = _raw_query_one(
            audit_db,
            "SELECT * FROM channel_audit_log WHERE id = ?",
            (event_id,),
        )
        assert row["severity"] == "warn"  # AI actor -> elevated

    def test_get_events_filters_by_channel(self, audit_logger):
        """get_events(channel_id=...) returns only matching events."""
        audit_logger.log_sync("ch-team", "push", items_affected=10)
        audit_logger.log_sync("ch-personal", "pull", items_affected=5)
        audit_logger.log_sync("ch-team", "pull", items_affected=3)

        events = audit_logger.get_events(channel_id="ch-team")
        assert len(events) == 2
        assert all(e.channel_id == "ch-team" for e in events)

    def test_get_events_filters_by_operation(self, audit_logger):
        """get_events(operation=...) returns only matching operations."""
        audit_logger.log_sync("ch-team", "push")
        audit_logger.log_share("ch-team", "node-2")
        audit_logger.log_retract("ch-team")

        events = audit_logger.get_events(operation="sync")
        assert len(events) == 1
        assert events[0].operation == "sync"

    def test_get_events_filters_by_outcome(self, audit_logger):
        """get_events(outcome=...) returns only matching outcomes."""
        audit_logger.log_sync("ch-team", "push", outcome="success")
        audit_logger.log_sync("ch-team", "pull", outcome="failed")

        events = audit_logger.get_events(outcome="failed")
        assert len(events) == 1
        assert events[0].outcome == "failed"

    def test_replay_returns_chronological_order(self, audit_logger):
        """replay() returns events oldest-first for natural reading."""
        audit_logger.log_sync("ch-team", "push", items_affected=10)
        audit_logger.log_share("ch-team", "node-2", items_affected=5)
        audit_logger.log_retract("ch-team", items_affected=2)

        events = audit_logger.replay(channel_id="ch-team")
        assert len(events) == 3
        # Chronological: oldest first
        assert events[0].operation == "sync"
        assert events[1].operation == "share"
        assert events[2].operation == "retract"

    def test_replay_ai_only_filter(self, audit_logger):
        """replay(ai_only=True) returns only agent/ai/daemon events."""
        audit_logger.log_sync(
            "ch-team", "push", actor="user-1", actor_type="user",
        )
        audit_logger.log_sync(
            "ch-team", "pull", actor="dispatch-1", actor_type="agent",
        )
        audit_logger.log_review(
            "ch-team", actor="review-daemon", actor_type="daemon",
        )

        events = audit_logger.replay(channel_id="ch-team", ai_only=True)
        assert len(events) == 2
        assert all(e.actor_type in ("agent", "ai", "daemon") for e in events)

    def test_format_event_includes_key_fields(self, audit_logger):
        """format_event() produces readable output with all key fields."""
        audit_logger.log_sync(
            "ch-team", "push",
            items_affected=42,
            actor="dispatch-1",
            actor_type="agent",
            detail="Synced 42 items push",
        )

        events = audit_logger.get_events(channel_id="ch-team")
        assert len(events) == 1

        formatted = format_event(events[0])
        assert "ch-team" in formatted
        assert "synced" in formatted
        assert "push" in formatted
        assert "42 items" in formatted
        assert "dispatch-1" in formatted

    def test_format_events_batch(self, audit_logger):
        """format_events() formats a list of events."""
        audit_logger.log_sync("ch-team", "push", items_affected=10)
        audit_logger.log_share("ch-team", "node-2", items_affected=5)

        events = audit_logger.replay(channel_id="ch-team")
        lines = format_events(events)
        assert len(lines) == 2
        assert all(isinstance(line, str) for line in lines)

    def test_format_replay_report_structure(self, audit_logger):
        """format_replay_report() produces structured report with summary."""
        audit_logger.log_sync(
            "ch-team", "push", items_affected=10,
            actor="dispatch-1", actor_type="agent",
        )
        audit_logger.log_share(
            "ch-team", "node-2",
            outcome="denied",
            detail="Insufficient permissions",
        )

        events = audit_logger.replay(channel_id="ch-team")
        report = format_replay_report(events)

        assert "CHANNEL AUDIT REPLAY REPORT" in report
        assert "Events: 2" in report
        assert "SUMMARY" in report
        assert "FULL TIMELINE" in report
        assert "FAILURES / DENIALS" in report  # denied share event

    def test_format_replay_report_empty(self):
        """format_replay_report() handles empty event list."""
        report = format_replay_report([])
        assert report == "No audit events to replay."

    def test_parent_event_linking(self, audit_db, audit_logger):
        """Events can reference a parent event for sub-operation tracking."""
        parent_id = audit_logger.log_sync(
            "ch-team", "bidirectional",
            detail="Parent sync operation",
        )

        child_id = audit_logger.log_sync(
            "ch-team", "push",
            items_affected=5,
            detail="Push phase of bidirectional sync",
            parent_event_id=parent_id,
        )

        child_row = _raw_query_one(
            audit_db,
            "SELECT * FROM channel_audit_log WHERE id = ?",
            (child_id,),
        )
        assert child_row["parent_event_id"] == parent_id

    def test_fail_silent_returns_none_on_error(self, audit_db):
        """ChannelAuditLogger(fail_silent=True) returns None on write failure."""
        # Drop the table to force an error
        audit_db._conn.execute("DROP TABLE channel_audit_log")
        audit_db._conn.commit()

        logger = ChannelAuditLogger(audit_db, fail_silent=True)
        result = logger.log_sync("ch-test", "push")
        assert result is None

    def test_fail_loud_raises_on_error(self, audit_db):
        """ChannelAuditLogger(fail_silent=False) propagates write errors."""
        audit_db._conn.execute("DROP TABLE channel_audit_log")
        audit_db._conn.commit()

        logger = ChannelAuditLogger(audit_db, fail_silent=False)
        with pytest.raises(Exception):
            logger.log_sync("ch-test", "push")


# ---------------------------------------------------------------------------
# 3. SecurityAudit Write-Read Integration
# ---------------------------------------------------------------------------


class TestSecurityAuditWriteRead:
    """Verify security_audit write -> read roundtrip via file-backed DB."""

    def test_log_detection_persists_event(self, security_db_path):
        """log_detection() writes a detection event to _security_audit."""
        with patch.dict(os.environ, {"GM_DB_PATH": security_db_path}):
            log_detection(
                hook_phase="pre",
                tool_name="Bash",
                pattern_name="aws_access_key",
                severity="critical",
                confidence=0.95,
                outcome="blocked",
                content="AKIAIOSFODNN7EXAMPLE",
                session_id="sess-001",
            )

            events = get_recent_events(limit=10)
            assert len(events) >= 1

            event = events[0]
            assert event["event_type"] == "detection"
            assert event["hook_phase"] == "pre"
            assert event["tool_name"] == "Bash"
            assert event["pattern_name"] == "aws_access_key"
            assert event["severity"] == "critical"
            assert event["confidence"] == pytest.approx(0.95)
            assert event["outcome"] == "blocked"
            assert event["session_id"] == "sess-001"
            assert event["content_hash"] is not None
            assert event["created_at"] is not None

    def test_log_policy_decision_persists_event(self, security_db_path):
        """log_policy_decision() writes a policy event to _security_audit."""
        with patch.dict(os.environ, {"GM_DB_PATH": security_db_path}):
            log_policy_decision(
                hook_phase="pre",
                tool_name="gm_knowledge",
                rule_id="r-block-secrets",
                outcome="refuse",
                reason="Content contains sensitive data",
                content="secret password 12345",
                session_id="sess-002",
            )

            events = get_recent_events(limit=10)
            assert len(events) >= 1

            event = events[0]
            assert event["event_type"] == "policy"
            assert event["severity"] == "high"  # refuse -> high
            assert event["outcome"] == "refuse"
            assert event["rule_id"] == "r-block-secrets"
            assert event["reason"] == "Content contains sensitive data"

    def test_log_policy_decision_allow_severity_info(self, security_db_path):
        """log_policy_decision() with allow outcome sets severity=info."""
        with patch.dict(os.environ, {"GM_DB_PATH": security_db_path}):
            log_policy_decision(
                hook_phase="post",
                tool_name="gm_observe",
                rule_id="r-default",
                outcome="allow",
            )

            events = get_recent_events(limit=10)
            assert len(events) >= 1
            assert events[0]["severity"] == "info"

    def test_multiple_events_accumulate(self, security_db_path):
        """Multiple log calls accumulate events in order."""
        with patch.dict(os.environ, {"GM_DB_PATH": security_db_path}):
            log_detection(
                hook_phase="pre", tool_name="Edit",
                pattern_name="private_key", severity="critical",
                confidence=0.99, outcome="blocked",
            )
            log_detection(
                hook_phase="pre", tool_name="Write",
                pattern_name="api_token", severity="high",
                confidence=0.85, outcome="blocked",
            )
            log_policy_decision(
                hook_phase="pre", tool_name="Bash",
                rule_id="r-no-rm-rf", outcome="refuse",
                reason="Destructive command",
            )

            events = get_recent_events(limit=50)
            assert len(events) == 3

    def test_get_recent_events_severity_filter(self, security_db_path):
        """get_recent_events(severity=...) filters correctly."""
        with patch.dict(os.environ, {"GM_DB_PATH": security_db_path}):
            log_detection(
                hook_phase="pre", tool_name="Bash",
                pattern_name="aws_key", severity="critical",
                confidence=0.9, outcome="blocked",
            )
            log_policy_decision(
                hook_phase="post", tool_name="gm_observe",
                rule_id="r-default", outcome="allow",
            )

            critical_events = get_recent_events(severity="critical")
            assert len(critical_events) == 1
            assert critical_events[0]["severity"] == "critical"

            info_events = get_recent_events(severity="info")
            assert len(info_events) == 1
            assert info_events[0]["severity"] == "info"

    def test_get_stats_reflects_writes(self, security_db_path):
        """get_stats() returns correct aggregate counts."""
        with patch.dict(os.environ, {"GM_DB_PATH": security_db_path}):
            log_detection(
                hook_phase="pre", tool_name="Bash",
                pattern_name="aws_key", severity="critical",
                confidence=0.9, outcome="blocked",
            )
            log_detection(
                hook_phase="pre", tool_name="Edit",
                pattern_name="ssh_key", severity="high",
                confidence=0.8, outcome="blocked",
            )
            log_policy_decision(
                hook_phase="post", tool_name="gm_knowledge",
                rule_id="r-default", outcome="allow",
            )

            stats = get_stats()
            assert stats.get("total_events", 0) == 3
            assert stats.get("blocked_count", 0) == 2
            assert "by_severity" in stats
            assert stats["by_severity"].get("critical", 0) == 1
            assert stats["by_severity"].get("high", 0) == 1
            assert stats["by_severity"].get("info", 0) == 1

    def test_content_hash_deterministic(self):
        """_content_hash() produces consistent, truncated SHA-256."""
        h1 = _content_hash("AKIAIOSFODNN7EXAMPLE")
        h2 = _content_hash("AKIAIOSFODNN7EXAMPLE")
        assert h1 == h2
        assert len(h1) == 16  # Truncated to 16 chars

    def test_content_hash_different_inputs(self):
        """_content_hash() produces different hashes for different inputs."""
        h1 = _content_hash("secret1")
        h2 = _content_hash("secret2")
        assert h1 != h2

    def test_get_recent_events_nonexistent_db(self, tmp_path):
        """get_recent_events() returns empty list when DB doesn't exist."""
        with patch.dict(os.environ, {"GM_DB_PATH": str(tmp_path / "nonexistent.db")}):
            events = get_recent_events()
            assert events == []

    def test_get_stats_nonexistent_db(self, tmp_path):
        """get_stats() returns empty dict when DB doesn't exist."""
        with patch.dict(os.environ, {"GM_DB_PATH": str(tmp_path / "nonexistent.db")}):
            stats = get_stats()
            assert stats == {}


# ---------------------------------------------------------------------------
# 4. Cross-Module: Benchmarks -> Standards -> Quality Pipeline
# ---------------------------------------------------------------------------


class TestBenchmarkStandardsQualityPipeline:
    """Verify write consistency across benchmarks, standards, and quality modules."""

    def test_benchmark_scored_against_standards(self, db, benchmarks, standards, quality):
        """Full pipeline: seed standards -> benchmark run -> score against each."""
        # Seed standards
        standards.create(
            standard_id="qs-pipeline-001", category="testing",
            rule="100% write path coverage", severity="critical",
        )
        standards.create(
            standard_id="qs-pipeline-002", category="security",
            rule="No hardcoded secrets", severity="critical",
        )
        standards.create(
            standard_id="qs-pipeline-003", category="style",
            rule="Consistent naming", severity="important",
        )

        # Start and complete a benchmark run
        run = benchmarks.start_run("greymatter", "Write integration tests")
        benchmarks.complete_run(run["id"], lines=400, tests=20, passing=20, first_pass=True)

        # Score against each standard
        benchmarks.score_run(run["id"], "qs-pipeline-001", passed=True, found_by="test_suite")
        benchmarks.score_run(run["id"], "qs-pipeline-002", passed=True, found_by="scanner")
        benchmarks.score_run(run["id"], "qs-pipeline-003", passed=False, found_by="code_review",
                             detail="Inconsistent variable naming in 2 files")

        # Verify all scores persisted and linked to correct standard
        scores = benchmarks.get_scores(run["id"])
        assert len(scores) == 3
        passed_count = sum(1 for s in scores if s["passed"])
        failed_count = sum(1 for s in scores if not s["passed"])
        assert passed_count == 2
        assert failed_count == 1

        # Verify the failed score has detail
        failed_score = next(s for s in scores if not s["passed"])
        assert failed_score["standard_id"] == "qs-pipeline-003"
        assert "Inconsistent" in failed_score["detail"]

        # Analytics reflect the scoring
        fpq = benchmarks.first_pass_quality(approach="greymatter")
        # 2 passed out of 3 scored -> 66.67%
        assert len(fpq) == 1
        assert fpq[0]["first_pass_pct"] == pytest.approx(66.67, abs=0.5)

    def test_deactivated_standard_scoring_still_persists(self, db, benchmarks, standards):
        """Scores against deactivated standards still persist in quality_scores."""
        standards.create(
            standard_id="qs-deact-001", category="testing",
            rule="Deactivation test", severity="critical",
        )

        run = benchmarks.start_run("greymatter", "Deactivation test")
        benchmarks.complete_run(run["id"], lines=100, tests=5, passing=5, first_pass=True)
        benchmarks.score_run(run["id"], "qs-deact-001", passed=True, found_by="review")

        # Deactivate the standard
        standards.deactivate("qs-deact-001")

        # Score still exists (historical data preserved)
        scores = benchmarks.get_scores(run["id"])
        assert len(scores) == 1
        assert scores[0]["standard_id"] == "qs-deact-001"

    def test_benchmark_with_knowledge_quality_check(self, db, benchmarks, knowledge, quality):
        """Write pipeline: create knowledge -> quality check -> benchmark."""
        # Create knowledge entries
        knowledge.create(
            entry_type="expertise", title="WAL Mode Benefits",
            content="SQLite WAL allows concurrent reads during writes",
            domain="database",
        )
        knowledge.create(
            entry_type="expertise", title="WAL Mode Trade-offs",
            content="WAL mode benefits for concurrent read access",
            domain="database",
        )

        # Quality check detects entries and potential issues
        report = quality.full_check()
        assert report.total_entries >= 2

        # Benchmark the quality process
        run = benchmarks.start_run("greymatter", "Quality audit")
        benchmarks.complete_run(run["id"], lines=50, tests=2, passing=2, first_pass=True)

        # Record any quality issues found as benchmark issues
        duplicate_issues = [i for i in report.issues if i.issue_type == "duplicate"]
        if duplicate_issues:
            benchmarks.record_issue(
                run["id"], "minor", "dedup",
                f"Found {len(duplicate_issues)} potential duplicate(s)",
                "quality_engine",
            )

        # Verify the pipeline connected: knowledge -> quality -> benchmark
        completed = benchmarks.get_run(run["id"])
        assert completed["completed_at"] is not None

    def test_work_item_benchmark_scoring_pipeline(
        self, db, benchmarks, standards, work_items,
    ):
        """Pipeline: work item -> benchmark -> score -> issue -> resolve."""
        # Create a work item
        wi = work_items.create(
            title="Implement write integration tests",
            project="greymatter",
        )
        work_items.update_status(wi["id"], "in_progress")

        # Benchmark the work
        run = benchmarks.start_run(
            "greymatter", "Write integration tests",
            session_id=None,
        )
        benchmarks.complete_run(run["id"], lines=500, tests=30, passing=28, first_pass=False)

        # Score against standards
        standards.create(
            standard_id="qs-wi-001", category="testing",
            rule="All tests must pass", severity="critical",
        )
        benchmarks.score_run(
            run["id"], "qs-wi-001", passed=False,
            found_by="test_runner",
            detail="2 tests failing",
        )

        # Record the failing tests as issues
        issue_id = benchmarks.record_issue(
            run["id"], "critical", "testing",
            "2 test failures in write integration suite",
            "test_runner",
        )

        # Fix and resolve
        benchmarks.resolve_issue(issue_id, fix_minutes=12)

        # Complete the work item
        work_items.complete(wi["id"])

        # Verify end-to-end state
        final_wi = work_items.get(wi["id"])
        assert final_wi["status"] == "completed"

        final_run = benchmarks.get_run(run["id"])
        assert final_run["completed_at"] is not None

        resolved_issue = _raw_query_one(
            db, "SELECT * FROM review_issues WHERE id = ?", (issue_id,),
        )
        assert resolved_issue["fixed_at"] is not None
        assert resolved_issue["time_to_fix_minutes"] == 12
