//! Storage query helper tests for the B.6 MCP surface.

use std::path::Path;

use loomweave_core::EdgeConfidence;
use loomweave_storage::{
    ModuleDependencyEdge, ReferenceDirection, RelationEdgeMatch, SubsystemMember, call_edges_from,
    call_edges_targeting, child_entity_ids, contained_entity_ids, containing_module_id, edge_total,
    entities_targeted_by_unresolved_call_sites, entity_at_line, entity_briefing_block_reason,
    entity_by_id, entity_total, find_entities, findings_for_emit, module_dependency_edges,
    module_reference_rollup, normalize_source_path, pragma, preferred_finding_anchor_by_file,
    reference_edges_for_entity, relation_edges_for_entity, resolve_file,
    resolve_file_catalog_entry, schema, stored_secret_finding_anchor_by_file, subsystem_for_member,
    subsystem_members, subsystem_of_entity, subsystem_total, unresolved_call_sites_for_caller,
    unresolved_caller_count_for_target, unresolved_callers_for_target,
};
use rusqlite::{Connection, params};

fn open_fresh(tempdir: &tempfile::TempDir) -> Connection {
    let path = tempdir.path().join("loomweave.db");
    let mut conn = Connection::open(path).expect("open sqlite");
    pragma::apply_write_pragmas(&conn).expect("write pragmas");
    schema::apply_migrations(&mut conn).expect("apply migrations");
    conn
}

fn insert_entity(conn: &Connection, id: &str, kind: &str) {
    insert_named_entity(conn, id, kind, id, id, None);
}

fn insert_entity_with_hash(conn: &Connection, id: &str, kind: &str, content_hash: &str) {
    conn.execute(
        "INSERT INTO entities (
            id, plugin_id, kind, name, short_name, properties, content_hash, created_at,
            updated_at
         ) VALUES (
            ?1, 'python', ?2, ?1, ?1, '{}', ?3,
            strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
            strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
         )",
        params![id, kind, content_hash],
    )
    .expect("insert entity with hash");
}

fn insert_entity_with_properties(
    conn: &Connection,
    id: &str,
    kind: &str,
    short_name: &str,
    properties_json: &str,
) {
    conn.execute(
        "INSERT INTO entities (
            id, plugin_id, kind, name, short_name, properties, created_at, updated_at
         ) VALUES (
            ?1, 'python', ?2, ?1, ?3, ?4,
            strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
            strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
         )",
        params![id, kind, short_name, properties_json],
    )
    .expect("insert entity with properties");
}

fn insert_named_entity(
    conn: &Connection,
    id: &str,
    kind: &str,
    name: &str,
    short_name: &str,
    source_file_path: Option<&str>,
) {
    conn.execute(
        "INSERT INTO entities (
            id, plugin_id, kind, name, short_name, source_file_path, properties, created_at,
            updated_at
         ) VALUES (
            ?1, 'python', ?2, ?3, ?4, ?5, '{}',
            strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
            strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
         )",
        params![id, kind, name, short_name, source_file_path],
    )
    .expect("insert entity");
}

fn insert_entity_with_range(
    conn: &Connection,
    id: &str,
    kind: &str,
    source_path: &Path,
    start_line: i64,
    end_line: i64,
) {
    conn.execute(
        "INSERT INTO entities (
            id, plugin_id, kind, name, short_name, source_file_path,
            source_line_start, source_line_end, properties, created_at, updated_at
         ) VALUES (
            ?1, 'python', ?2, ?1, ?1, ?3, ?4, ?5, '{}',
            strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
            strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
         )",
        params![
            id,
            kind,
            source_path.display().to_string(),
            start_line,
            end_line
        ],
    )
    .expect("insert ranged entity");
}

fn insert_calls_edge(
    conn: &Connection,
    from_id: &str,
    to_id: &str,
    confidence: EdgeConfidence,
    candidates: &[&str],
) {
    let properties = if candidates.is_empty() {
        None
    } else {
        Some(serde_json::json!({ "candidates": candidates }).to_string())
    };
    conn.execute(
        "INSERT INTO edges (
            kind, from_id, to_id, confidence, properties, source_byte_start, source_byte_end
         ) VALUES ('calls', ?1, ?2, ?3, ?4, 10, 20)",
        params![from_id, to_id, confidence.as_str(), properties],
    )
    .expect("insert calls edge");
}

fn insert_contains_edge(conn: &Connection, from_id: &str, to_id: &str) {
    conn.execute(
        "INSERT INTO edges (kind, from_id, to_id, confidence)
         VALUES ('contains', ?1, ?2, 'resolved')",
        params![from_id, to_id],
    )
    .expect("insert contains edge");
}

fn insert_unresolved_call_site(
    conn: &Connection,
    caller_id: &str,
    caller_content_hash: &str,
    site_key: &str,
    callee_expr: &str,
) {
    conn.execute(
        "INSERT INTO entity_unresolved_call_sites (
            caller_entity_id, caller_content_hash, site_key, site_ordinal,
            source_byte_start, source_byte_end, callee_expr, created_at
         ) VALUES (?1, ?2, ?3, 0, 10, 20, ?4, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))",
        params![caller_id, caller_content_hash, site_key, callee_expr],
    )
    .expect("insert unresolved call site");
}

fn insert_references_edge(
    conn: &Connection,
    from_id: &str,
    to_id: &str,
    confidence: EdgeConfidence,
    start: i64,
    end: i64,
) {
    conn.execute(
        "INSERT INTO edges (
            kind, from_id, to_id, confidence, source_byte_start, source_byte_end
         ) VALUES ('references', ?1, ?2, ?3, ?4, ?5)",
        params![from_id, to_id, confidence.as_str(), start, end],
    )
    .expect("insert references edge");
}

fn insert_relation_edge(
    conn: &Connection,
    kind: &str,
    from_id: &str,
    to_id: &str,
    confidence: EdgeConfidence,
    candidates: &[&str],
    start: i64,
    end: i64,
) {
    let properties = if candidates.is_empty() {
        None
    } else {
        Some(serde_json::json!({ "candidates": candidates }).to_string())
    };
    // The anchor-file FK target; relation anchors always carry a file
    // (anchored kinds, ADR-026).
    conn.execute(
        "INSERT OR IGNORE INTO entities (
            id, plugin_id, kind, name, short_name, properties, created_at, updated_at
         ) VALUES (
            'core:file:src/demo.py', 'core', 'file', 'src/demo.py', 'demo.py', '{}',
            strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
            strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
         )",
        [],
    )
    .expect("insert anchor file entity");
    conn.execute(
        "INSERT INTO edges (
            kind, from_id, to_id, confidence, properties, source_file_id,
            source_byte_start, source_byte_end
         ) VALUES (?1, ?2, ?3, ?4, ?5, 'core:file:src/demo.py', ?6, ?7)",
        params![
            kind,
            from_id,
            to_id,
            confidence.as_str(),
            properties,
            start,
            end
        ],
    )
    .expect("insert relation edge");
}

fn insert_imports_edge(conn: &Connection, from_id: &str, to_id: &str) {
    conn.execute(
        "INSERT INTO edges (
            kind, from_id, to_id, confidence, source_byte_start, source_byte_end
         ) VALUES ('imports', ?1, ?2, 'resolved', 30, 40)",
        params![from_id, to_id],
    )
    .expect("insert imports edge");
}

fn insert_in_subsystem_edge(conn: &Connection, module_id: &str, subsystem_id: &str) {
    conn.execute(
        "INSERT INTO edges (kind, from_id, to_id, confidence)
         VALUES ('in_subsystem', ?1, ?2, 'resolved')",
        params![module_id, subsystem_id],
    )
    .expect("insert in_subsystem edge");
}

#[test]
fn module_dependency_edges_include_imports() {
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity(&conn, "python:module:pkg.alpha", "module");
    insert_entity(&conn, "python:module:pkg.beta", "module");
    insert_imports_edge(&conn, "python:module:pkg.alpha", "python:module:pkg.beta");

    let edges = module_dependency_edges(&conn, &["imports"]).expect("module dependency edges");

    assert_eq!(
        edges,
        vec![ModuleDependencyEdge {
            from_module_id: "python:module:pkg.alpha".to_owned(),
            to_module_id: "python:module:pkg.beta".to_owned(),
            reference_count: 1,
            edge_kinds: vec!["imports".to_owned()],
        }],
    );
}

#[test]
fn module_dependency_edges_roll_up_function_calls_to_parent_modules() {
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    for id in ["python:module:pkg.alpha", "python:module:pkg.beta"] {
        insert_entity(&conn, id, "module");
    }
    for id in [
        "python:function:pkg.alpha.source",
        "python:function:pkg.beta.target",
    ] {
        insert_entity(&conn, id, "function");
    }
    insert_contains_edge(
        &conn,
        "python:module:pkg.alpha",
        "python:function:pkg.alpha.source",
    );
    insert_contains_edge(
        &conn,
        "python:module:pkg.beta",
        "python:function:pkg.beta.target",
    );
    insert_calls_edge(
        &conn,
        "python:function:pkg.alpha.source",
        "python:function:pkg.beta.target",
        EdgeConfidence::Resolved,
        &[],
    );

    let edges = module_dependency_edges(&conn, &["calls"]).expect("module dependency edges");

    assert_eq!(
        edges,
        vec![ModuleDependencyEdge {
            from_module_id: "python:module:pkg.alpha".to_owned(),
            to_module_id: "python:module:pkg.beta".to_owned(),
            reference_count: 1,
            edge_kinds: vec!["calls".to_owned()],
        }],
    );
}

#[test]
fn module_dependency_edges_weight_by_reference_count() {
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    for id in ["python:module:pkg.alpha", "python:module:pkg.beta"] {
        insert_entity(&conn, id, "module");
    }
    for id in [
        "python:function:pkg.alpha.first",
        "python:function:pkg.alpha.second",
        "python:function:pkg.beta.target",
    ] {
        insert_entity(&conn, id, "function");
    }
    insert_contains_edge(
        &conn,
        "python:module:pkg.alpha",
        "python:function:pkg.alpha.first",
    );
    insert_contains_edge(
        &conn,
        "python:module:pkg.alpha",
        "python:function:pkg.alpha.second",
    );
    insert_contains_edge(
        &conn,
        "python:module:pkg.beta",
        "python:function:pkg.beta.target",
    );
    insert_calls_edge(
        &conn,
        "python:function:pkg.alpha.first",
        "python:function:pkg.beta.target",
        EdgeConfidence::Resolved,
        &[],
    );
    insert_calls_edge(
        &conn,
        "python:function:pkg.alpha.second",
        "python:function:pkg.beta.target",
        EdgeConfidence::Resolved,
        &[],
    );
    insert_imports_edge(&conn, "python:module:pkg.alpha", "python:module:pkg.beta");

    let edges =
        module_dependency_edges(&conn, &["imports", "calls"]).expect("module dependency edges");

    assert_eq!(
        edges,
        vec![ModuleDependencyEdge {
            from_module_id: "python:module:pkg.alpha".to_owned(),
            to_module_id: "python:module:pkg.beta".to_owned(),
            reference_count: 3,
            edge_kinds: vec!["calls".to_owned(), "imports".to_owned()],
        }],
    );
}

#[test]
fn module_dependency_edges_skip_self_edges() {
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity(&conn, "python:module:pkg.alpha", "module");
    insert_entity(&conn, "python:function:pkg.alpha.first", "function");
    insert_entity(&conn, "python:function:pkg.alpha.second", "function");
    insert_contains_edge(
        &conn,
        "python:module:pkg.alpha",
        "python:function:pkg.alpha.first",
    );
    insert_contains_edge(
        &conn,
        "python:module:pkg.alpha",
        "python:function:pkg.alpha.second",
    );
    insert_calls_edge(
        &conn,
        "python:function:pkg.alpha.first",
        "python:function:pkg.alpha.second",
        EdgeConfidence::Resolved,
        &[],
    );
    insert_imports_edge(&conn, "python:module:pkg.alpha", "python:module:pkg.alpha");

    let edges =
        module_dependency_edges(&conn, &["imports", "calls"]).expect("module dependency edges");

    assert!(edges.is_empty());
}

#[test]
fn module_dependency_edges_exclude_inferred_calls() {
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    for id in ["python:module:pkg.alpha", "python:module:pkg.beta"] {
        insert_entity(&conn, id, "module");
    }
    for id in [
        "python:function:pkg.alpha.source",
        "python:function:pkg.beta.target",
    ] {
        insert_entity(&conn, id, "function");
    }
    insert_contains_edge(
        &conn,
        "python:module:pkg.alpha",
        "python:function:pkg.alpha.source",
    );
    insert_contains_edge(
        &conn,
        "python:module:pkg.beta",
        "python:function:pkg.beta.target",
    );
    insert_calls_edge(
        &conn,
        "python:function:pkg.alpha.source",
        "python:function:pkg.beta.target",
        EdgeConfidence::Inferred,
        &[],
    );

    let edges = module_dependency_edges(&conn, &["calls"]).expect("module dependency edges");

    assert!(
        edges.is_empty(),
        "query-time inferred calls must not contaminate Phase 3 clustering input"
    );
}

#[test]
fn module_dependency_edges_expands_ambiguous_call_candidates() {
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    for id in [
        "python:module:pkg.alpha",
        "python:module:pkg.beta",
        "python:module:pkg.gamma",
    ] {
        insert_entity(&conn, id, "module");
    }
    for id in [
        "python:function:pkg.alpha.source",
        "python:function:pkg.beta.first",
        "python:function:pkg.gamma.second",
    ] {
        insert_entity(&conn, id, "function");
    }
    insert_contains_edge(
        &conn,
        "python:module:pkg.alpha",
        "python:function:pkg.alpha.source",
    );
    insert_contains_edge(
        &conn,
        "python:module:pkg.beta",
        "python:function:pkg.beta.first",
    );
    insert_contains_edge(
        &conn,
        "python:module:pkg.gamma",
        "python:function:pkg.gamma.second",
    );
    insert_calls_edge(
        &conn,
        "python:function:pkg.alpha.source",
        "python:function:pkg.beta.first",
        EdgeConfidence::Ambiguous,
        &[
            "python:function:pkg.beta.first",
            "python:function:pkg.gamma.second",
        ],
    );

    let edges = module_dependency_edges(&conn, &["calls"]).expect("module dependency edges");

    assert_eq!(
        edges,
        vec![
            ModuleDependencyEdge {
                from_module_id: "python:module:pkg.alpha".to_owned(),
                to_module_id: "python:module:pkg.beta".to_owned(),
                reference_count: 1,
                edge_kinds: vec!["calls".to_owned()],
            },
            ModuleDependencyEdge {
                from_module_id: "python:module:pkg.alpha".to_owned(),
                to_module_id: "python:module:pkg.gamma".to_owned(),
                reference_count: 1,
                edge_kinds: vec!["calls".to_owned()],
            },
        ],
    );
}

#[test]
fn unresolved_call_sites_for_caller_filters_stale_content_hash_rows() {
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity_with_hash(
        &conn,
        "python:function:pkg.caller",
        "function",
        "hash-current",
    );
    insert_unresolved_call_site(
        &conn,
        "python:function:pkg.caller",
        "hash-old",
        "stale-site",
        "dynamic_old",
    );
    insert_unresolved_call_site(
        &conn,
        "python:function:pkg.caller",
        "hash-current",
        "current-site",
        "dynamic_current",
    );

    let sites = unresolved_call_sites_for_caller(&conn, "python:function:pkg.caller", 10).unwrap();

    assert_eq!(sites.len(), 1);
    assert_eq!(sites[0].site_key, "current-site");
    assert_eq!(sites[0].caller_content_hash, "hash-current");
}

#[test]
fn unresolved_callers_for_target_filters_stale_caller_content_hash_rows() {
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity_with_hash(
        &conn,
        "python:function:pkg.caller",
        "function",
        "hash-current",
    );
    insert_entity_with_hash(
        &conn,
        "python:function:pkg.target",
        "function",
        "hash-target",
    );
    insert_unresolved_call_site(
        &conn,
        "python:function:pkg.caller",
        "hash-old",
        "stale-site",
        "target",
    );
    insert_unresolved_call_site(
        &conn,
        "python:function:pkg.caller",
        "hash-current",
        "current-site",
        "target",
    );
    let target = entity_by_id(&conn, "python:function:pkg.target")
        .unwrap()
        .expect("target");

    let sites = unresolved_callers_for_target(&conn, &target, 10).unwrap();

    assert_eq!(sites.len(), 1);
    assert_eq!(sites[0].site_key, "current-site");
    assert_eq!(sites[0].caller_content_hash, "hash-current");
}

#[test]
fn unresolved_callers_for_target_matches_rust_colon_path_callees() {
    // A Rust unresolved call site like `Type::assoc()` or `crate::m::f()` stores
    // a `::`-joined `callee_expr` (`"Svc::make"`, `"crate::m::f"`). The terminal
    // identifier is the leaf after the last `::` (`make`, `f`) — but the old
    // matcher only tested `= leaf`, `= name`, and `LIKE %.leaf`, so a `::`-suffixed
    // callee matched none of them and the `assoc`/`f` target reported ZERO
    // unresolved callers. Both the page reader and the unbounded honesty count
    // must now see the `%::leaf` form.
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity_with_hash(&conn, "rust:function:app.run", "function", "hc");
    insert_named_entity(
        &conn,
        "rust:function:app.Svc.impl#<>.make",
        "function",
        "app.Svc.impl#<>.make",
        "make",
        None,
    );
    // Associated-function path `Svc::make`; deeper module path `crate::m::make`.
    insert_unresolved_call_site(&conn, "rust:function:app.run", "hc", "s0", "Svc::make");
    insert_unresolved_call_site(&conn, "rust:function:app.run", "hc", "s1", "crate::m::make");

    let target = entity_by_id(&conn, "rust:function:app.Svc.impl#<>.make")
        .unwrap()
        .expect("target");

    let sites = unresolved_callers_for_target(&conn, &target, 10).unwrap();
    assert_eq!(
        sites.len(),
        2,
        "both `::`-path callees must match the `make` target: {sites:?}"
    );

    let count = unresolved_caller_count_for_target(&conn, &target).unwrap();
    assert_eq!(
        count, 2,
        "the unresolved_name_matches honesty count must also see `::`-path callees"
    );
}

#[test]
fn entities_targeted_by_unresolved_sites_matches_method_and_assoc_leaves() {
    // The dead-code fail-toward-live input: an entity whose short_name matches
    // the terminal identifier of an unresolved call site is a plausible target.
    // Critically this must match the Rust associated-function `::` form
    // (`Svc::make` -> `make`), which the `.`-only leaf extraction in
    // `candidate_entities_for_expr` / `unresolved_callers_for_target` misses.
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity_with_hash(&conn, "rust:function:app.run", "function", "hc");
    insert_named_entity(
        &conn,
        "rust:function:app.Svc.impl#<>.do_work",
        "function",
        "app.Svc.impl#<>.do_work",
        "do_work",
        None,
    );
    insert_named_entity(
        &conn,
        "rust:function:app.Svc.impl#<>.make",
        "function",
        "app.Svc.impl#<>.make",
        "make",
        None,
    );
    insert_named_entity(
        &conn,
        "rust:function:app.helper",
        "function",
        "app.helper",
        "helper",
        None,
    );
    insert_named_entity(
        &conn,
        "rust:function:app.orphan",
        "function",
        "app.orphan",
        "orphan",
        None,
    );

    // Method call `.do_work`; assoc path `Svc::make`; dotted path `m.helper`;
    // a non-identifier form `<expr>()` (contributes no leaf).
    insert_unresolved_call_site(&conn, "rust:function:app.run", "hc", "s0", ".do_work");
    insert_unresolved_call_site(&conn, "rust:function:app.run", "hc", "s1", "Svc::make");
    insert_unresolved_call_site(&conn, "rust:function:app.run", "hc", "s2", "m.helper");
    insert_unresolved_call_site(&conn, "rust:function:app.run", "hc", "s3", "<expr>()");

    let targeted = entities_targeted_by_unresolved_call_sites(&conn).unwrap();

    assert!(
        targeted.contains("rust:function:app.Svc.impl#<>.do_work"),
        "method-call leaf `.do_work` must target `do_work`: {targeted:?}"
    );
    assert!(
        targeted.contains("rust:function:app.Svc.impl#<>.make"),
        "assoc-call leaf `Svc::make` must target `make` (the :: form the `.`-only \
         matchers miss): {targeted:?}"
    );
    assert!(
        targeted.contains("rust:function:app.helper"),
        "dotted-path leaf `m.helper` must target `helper`: {targeted:?}"
    );
    assert!(
        !targeted.contains("rust:function:app.orphan"),
        "an entity matched by no call leaf must NOT be targeted: {targeted:?}"
    );
}

#[test]
fn entities_targeted_by_unresolved_sites_skips_stale_caller() {
    // A site whose caller content-hash is stale must not target anything
    // (same staleness filter as unresolved_callers_for_target).
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity_with_hash(&conn, "rust:function:app.run", "function", "hc");
    insert_named_entity(&conn, "rust:function:app.t", "function", "app.t", "t", None);
    insert_unresolved_call_site(&conn, "rust:function:app.run", "stale", "s0", ".t");

    let targeted = entities_targeted_by_unresolved_call_sites(&conn).unwrap();
    assert!(
        targeted.is_empty(),
        "a stale-caller site must not target anything: {targeted:?}"
    );
}

#[test]
fn subsystem_members_returns_modules_ordered_by_name() {
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_named_entity(
        &conn,
        "core:subsystem:abc123def456",
        "subsystem",
        "Auth subsystem",
        "Auth subsystem",
        None,
    );
    insert_named_entity(
        &conn,
        "python:module:pkg.beta",
        "module",
        "pkg.beta",
        "beta",
        Some("/tmp/pkg/beta.py"),
    );
    insert_named_entity(
        &conn,
        "python:module:pkg.alpha",
        "module",
        "pkg.alpha",
        "alpha",
        Some("/tmp/pkg/alpha.py"),
    );
    insert_in_subsystem_edge(
        &conn,
        "python:module:pkg.beta",
        "core:subsystem:abc123def456",
    );
    insert_in_subsystem_edge(
        &conn,
        "python:module:pkg.alpha",
        "core:subsystem:abc123def456",
    );

    let members =
        subsystem_members(&conn, "core:subsystem:abc123def456").expect("subsystem members");
    let subsystem =
        subsystem_for_member(&conn, "python:module:pkg.alpha").expect("subsystem for member");

    assert_eq!(
        members,
        vec![
            SubsystemMember {
                id: "python:module:pkg.alpha".to_owned(),
                name: "pkg.alpha".to_owned(),
                source_file_path: Some("/tmp/pkg/alpha.py".to_owned()),
            },
            SubsystemMember {
                id: "python:module:pkg.beta".to_owned(),
                name: "pkg.beta".to_owned(),
                source_file_path: Some("/tmp/pkg/beta.py".to_owned()),
            },
        ],
    );
    assert_eq!(subsystem, Some("core:subsystem:abc123def456".to_owned()));
    assert_eq!(
        subsystem_for_member(&conn, "python:module:pkg.gamma").expect("unknown member"),
        None,
    );
}

#[test]
fn reference_edges_for_entity_returns_directional_neighbors() {
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity(&conn, "python:function:demo.source", "function");
    insert_entity(&conn, "python:function:demo.target", "function");
    insert_entity(&conn, "python:function:demo.outbound", "function");
    insert_references_edge(
        &conn,
        "python:function:demo.source",
        "python:function:demo.target",
        EdgeConfidence::Resolved,
        20,
        25,
    );
    insert_references_edge(
        &conn,
        "python:function:demo.target",
        "python:function:demo.outbound",
        EdgeConfidence::Ambiguous,
        30,
        39,
    );

    let inbound =
        reference_edges_for_entity(&conn, "python:function:demo.target", ReferenceDirection::In)
            .expect("query inbound references");
    let outbound = reference_edges_for_entity(
        &conn,
        "python:function:demo.target",
        ReferenceDirection::Out,
    )
    .expect("query outbound references");

    assert_eq!(inbound.len(), 1);
    assert_eq!(inbound[0].neighbor_id, "python:function:demo.source");
    assert_eq!(inbound[0].confidence, EdgeConfidence::Resolved);
    assert_eq!(inbound[0].source_byte_start, Some(20));
    assert_eq!(inbound[0].source_byte_end, Some(25));

    assert_eq!(outbound.len(), 1);
    assert_eq!(outbound[0].neighbor_id, "python:function:demo.outbound");
    assert_eq!(outbound[0].confidence, EdgeConfidence::Ambiguous);
    assert_eq!(outbound[0].source_byte_start, Some(30));
    assert_eq!(outbound[0].source_byte_end, Some(39));
}

#[test]
fn relation_edges_for_entity_returns_directional_neighbors_across_kinds() {
    // "What subclasses Base" is a TO-side (In) lookup on inherits_from; "what
    // does wrap decorate" is a FROM-side (Out) lookup on decorates (ADR-051).
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity(&conn, "python:class:demo.Base", "class");
    insert_entity(&conn, "python:class:demo.Child", "class");
    insert_entity(&conn, "python:function:demo.wrap", "function");
    insert_entity(&conn, "python:function:demo.handler", "function");
    insert_relation_edge(
        &conn,
        "inherits_from",
        "python:class:demo.Child",
        "python:class:demo.Base",
        EdgeConfidence::Resolved,
        &[],
        100,
        117,
    );
    insert_relation_edge(
        &conn,
        "decorates",
        "python:function:demo.wrap",
        "python:function:demo.handler",
        EdgeConfidence::Resolved,
        &[],
        200,
        204,
    );
    // Non-relation kinds targeting the same entities must never leak into a
    // relation read (the inverse of the write-only gap this surface closes).
    insert_calls_edge(
        &conn,
        "python:function:demo.handler",
        "python:class:demo.Base",
        EdgeConfidence::Resolved,
        &[],
    );
    insert_references_edge(
        &conn,
        "python:class:demo.Child",
        "python:class:demo.Base",
        EdgeConfidence::Resolved,
        10,
        15,
    );

    let subclasses = relation_edges_for_entity(
        &conn,
        "python:class:demo.Base",
        ReferenceDirection::In,
        None,
    )
    .expect("inbound relations");
    assert_eq!(
        subclasses,
        vec![RelationEdgeMatch {
            kind: "inherits_from".to_owned(),
            from_id: "python:class:demo.Child".to_owned(),
            to_id: "python:class:demo.Base".to_owned(),
            confidence: EdgeConfidence::Resolved,
            candidates: Vec::new(),
            source_file_id: Some("core:file:src/demo.py".to_owned()),
            source_byte_start: Some(100),
            source_byte_end: Some(117),
        }],
        "In on Base must list the subclass edge only, never calls/references"
    );

    let supertypes = relation_edges_for_entity(
        &conn,
        "python:class:demo.Child",
        ReferenceDirection::Out,
        None,
    )
    .expect("outbound relations");
    assert_eq!(supertypes.len(), 1);
    assert_eq!(supertypes[0].kind, "inherits_from");
    assert_eq!(supertypes[0].to_id, "python:class:demo.Base");

    let decorated = relation_edges_for_entity(
        &conn,
        "python:function:demo.wrap",
        ReferenceDirection::Out,
        None,
    )
    .expect("outbound decorates");
    assert_eq!(decorated.len(), 1);
    assert_eq!(decorated[0].kind, "decorates");
    assert_eq!(decorated[0].to_id, "python:function:demo.handler");

    let decorators = relation_edges_for_entity(
        &conn,
        "python:function:demo.handler",
        ReferenceDirection::In,
        None,
    )
    .expect("inbound decorates");
    assert_eq!(decorators.len(), 1);
    assert_eq!(decorators[0].from_id, "python:function:demo.wrap");
}

#[test]
fn relation_edges_for_entity_kind_filter_and_deterministic_order() {
    // One entity targeted by all four relation kinds (a Rust trait can be both
    // implemented and derived); unfiltered reads order by kind then neighbor.
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity(&conn, "rust:trait:demo::Render", "trait");
    insert_entity(&conn, "rust:struct:demo::Panel", "struct");
    insert_entity(&conn, "rust:struct:demo::Badge", "struct");
    insert_entity(&conn, "python:class:demo.Widget", "class");
    insert_entity(&conn, "python:function:demo.deco", "function");
    insert_relation_edge(
        &conn,
        "implements",
        "rust:struct:demo::Panel",
        "rust:trait:demo::Render",
        EdgeConfidence::Resolved,
        &[],
        10,
        16,
    );
    insert_relation_edge(
        &conn,
        "derives",
        "rust:struct:demo::Badge",
        "rust:trait:demo::Render",
        EdgeConfidence::Resolved,
        &[],
        20,
        26,
    );
    insert_relation_edge(
        &conn,
        "inherits_from",
        "python:class:demo.Widget",
        "rust:trait:demo::Render",
        EdgeConfidence::Resolved,
        &[],
        30,
        36,
    );
    insert_relation_edge(
        &conn,
        "decorates",
        "python:function:demo.deco",
        "rust:trait:demo::Render",
        EdgeConfidence::Ambiguous,
        &["python:function:demo.deco", "python:function:demo.other"],
        40,
        44,
    );

    let all = relation_edges_for_entity(
        &conn,
        "rust:trait:demo::Render",
        ReferenceDirection::In,
        None,
    )
    .expect("all inbound relations");
    let kinds: Vec<&str> = all.iter().map(|m| m.kind.as_str()).collect();
    assert_eq!(
        kinds,
        vec!["decorates", "derives", "implements", "inherits_from"],
        "unfiltered relations order by kind then neighbor: {all:?}"
    );
    // Ambiguous rows carry their candidate ids through (sorted); resolved rows
    // carry none.
    assert_eq!(
        all[0].candidates,
        vec![
            "python:function:demo.deco".to_owned(),
            "python:function:demo.other".to_owned(),
        ],
    );
    assert!(all[1].candidates.is_empty());

    let implementors = relation_edges_for_entity(
        &conn,
        "rust:trait:demo::Render",
        ReferenceDirection::In,
        Some("implements"),
    )
    .expect("kind-filtered relations");
    assert_eq!(implementors.len(), 1);
    assert_eq!(implementors[0].from_id, "rust:struct:demo::Panel");

    let none = relation_edges_for_entity(
        &conn,
        "rust:trait:demo::Render",
        ReferenceDirection::Out,
        None,
    )
    .expect("outbound from a pure target");
    assert!(none.is_empty(), "Render originates no relation: {none:?}");
}

#[test]
fn relation_edges_for_entity_matches_ambiguous_candidate_endpoints() {
    // An ambiguous `implements` edge resolved to ONE same-named trait but listed
    // the alternative in `properties.candidates`. Querying inbound relations for
    // the alternative (the candidate, NOT the stored `to_id`) must surface the
    // edge — mirroring `call_edges_targeting`'s candidate pass. Before the fix
    // the direct `to_id = ?` filter returned nothing for the candidate trait.
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity(&conn, "rust:struct:demo::Panel", "struct");
    insert_entity(&conn, "rust:trait:a::Render", "trait");
    insert_entity(&conn, "rust:trait:b::Render", "trait");
    // The resolver keyed the edge to a::Render but kept b::Render as a candidate.
    insert_relation_edge(
        &conn,
        "implements",
        "rust:struct:demo::Panel",
        "rust:trait:a::Render",
        EdgeConfidence::Ambiguous,
        &["rust:trait:a::Render", "rust:trait:b::Render"],
        10,
        16,
    );

    // The stored endpoint resolves directly, as before.
    let direct =
        relation_edges_for_entity(&conn, "rust:trait:a::Render", ReferenceDirection::In, None)
            .expect("inbound for stored endpoint");
    assert_eq!(direct.len(), 1, "stored to_id still matches: {direct:?}");
    assert_eq!(direct[0].from_id, "rust:struct:demo::Panel");

    // The candidate endpoint now resolves through the candidate pass.
    let candidate =
        relation_edges_for_entity(&conn, "rust:trait:b::Render", ReferenceDirection::In, None)
            .expect("inbound for candidate endpoint");
    assert_eq!(
        candidate.len(),
        1,
        "an ambiguous candidate endpoint must surface the edge: {candidate:?}"
    );
    assert_eq!(candidate[0].kind, "implements");
    assert_eq!(candidate[0].from_id, "rust:struct:demo::Panel");
    assert_eq!(
        candidate[0].to_id, "rust:trait:a::Render",
        "the edge keeps its stored endpoints; the candidate match does not rewrite them",
    );

    // Kind-filter still narrows the candidate-matched set.
    let filtered = relation_edges_for_entity(
        &conn,
        "rust:trait:b::Render",
        ReferenceDirection::In,
        Some("derives"),
    )
    .expect("kind-filtered candidate read");
    assert!(
        filtered.is_empty(),
        "a non-matching kind filter drops the candidate edge: {filtered:?}"
    );

    // An edge that matches BOTH directly and via candidate is deduped to one.
    let deduped =
        relation_edges_for_entity(&conn, "rust:trait:a::Render", ReferenceDirection::In, None)
            .expect("inbound for endpoint also in its own candidates");
    assert_eq!(
        deduped.len(),
        1,
        "the edge appears once even though a::Render is both to_id and a candidate: {deduped:?}"
    );

    // Direction gating (Codex review): `implements` candidates are TARGET-side,
    // so b::Render answers an `In` lookup only. An `Out` query from b::Render
    // must NOT surface the edge — b::Render is not a source endpoint, so
    // returning it would make the renderer report a false outbound relation.
    let wrong_direction =
        relation_edges_for_entity(&conn, "rust:trait:b::Render", ReferenceDirection::Out, None)
            .expect("outbound for target-side candidate");
    assert!(
        wrong_direction.is_empty(),
        "a target-side candidate must not match an Out lookup: {wrong_direction:?}"
    );
}

#[test]
fn module_reference_rollup_aggregates_contained_symbol_edges_excluding_internal() {
    // A `from pkg.contracts import RunStatus` records a `references` edge to the
    // class, not the module — so the module's own edges are empty and the
    // rollup must aggregate contained symbols' edges (clarion-79d0ff6e14).
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);

    // Module under query, with two contained classes.
    insert_entity(&conn, "python:module:pkg.contracts", "module");
    insert_entity(&conn, "python:class:pkg.contracts.RunStatus", "class");
    insert_entity(&conn, "python:class:pkg.contracts.Helper", "class");
    insert_contains_edge(
        &conn,
        "python:module:pkg.contracts",
        "python:class:pkg.contracts.RunStatus",
    );
    insert_contains_edge(
        &conn,
        "python:module:pkg.contracts",
        "python:class:pkg.contracts.Helper",
    );

    // An external module whose function imports RunStatus (reverse-import In).
    insert_entity(&conn, "python:module:pkg.consumer", "module");
    insert_entity(&conn, "python:function:pkg.consumer.use", "function");
    insert_contains_edge(
        &conn,
        "python:module:pkg.consumer",
        "python:function:pkg.consumer.use",
    );
    insert_references_edge(
        &conn,
        "python:function:pkg.consumer.use",
        "python:class:pkg.contracts.RunStatus",
        EdgeConfidence::Resolved,
        20,
        25,
    );

    // A symbol the module's class references outward (rollup Out).
    insert_entity(&conn, "python:module:pkg.other", "module");
    insert_entity(&conn, "python:class:pkg.other.Thing", "class");
    insert_contains_edge(
        &conn,
        "python:module:pkg.other",
        "python:class:pkg.other.Thing",
    );
    insert_references_edge(
        &conn,
        "python:class:pkg.contracts.RunStatus",
        "python:class:pkg.other.Thing",
        EdgeConfidence::Resolved,
        30,
        40,
    );

    // Intra-module reference (RunStatus -> Helper): internal wiring, must be
    // excluded from BOTH directions of the rollup.
    insert_references_edge(
        &conn,
        "python:class:pkg.contracts.RunStatus",
        "python:class:pkg.contracts.Helper",
        EdgeConfidence::Resolved,
        50,
        55,
    );

    let inbound =
        module_reference_rollup(&conn, "python:module:pkg.contracts", ReferenceDirection::In)
            .expect("rollup inbound");
    assert_eq!(
        inbound.len(),
        1,
        "only the external referencer rolls up: {inbound:?}"
    );
    assert_eq!(inbound[0].neighbor_id, "python:function:pkg.consumer.use");
    assert_eq!(inbound[0].via_id, "python:class:pkg.contracts.RunStatus");
    assert_eq!(inbound[0].confidence, EdgeConfidence::Resolved);
    assert_eq!(inbound[0].source_byte_start, Some(20));

    let outbound = module_reference_rollup(
        &conn,
        "python:module:pkg.contracts",
        ReferenceDirection::Out,
    )
    .expect("rollup outbound");
    assert_eq!(
        outbound.len(),
        1,
        "only the external reference rolls up: {outbound:?}"
    );
    assert_eq!(outbound[0].neighbor_id, "python:class:pkg.other.Thing");
    assert_eq!(outbound[0].via_id, "python:class:pkg.contracts.RunStatus");
}

#[test]
fn module_reference_rollup_returns_empty_for_module_with_no_contained_edges() {
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity(&conn, "python:module:pkg.lonely", "module");
    insert_entity(&conn, "python:class:pkg.lonely.Isolated", "class");
    insert_contains_edge(
        &conn,
        "python:module:pkg.lonely",
        "python:class:pkg.lonely.Isolated",
    );

    let inbound =
        module_reference_rollup(&conn, "python:module:pkg.lonely", ReferenceDirection::In)
            .expect("rollup inbound");
    assert!(
        inbound.is_empty(),
        "no references means an empty rollup, not an error"
    );
}

#[test]
fn call_edges_targeting_expands_candidate_only_ambiguous_targets() {
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity(&conn, "python:function:demo.caller", "function");
    insert_entity(&conn, "python:function:demo.alpha", "function");
    insert_entity(&conn, "python:function:demo.beta", "function");
    insert_calls_edge(
        &conn,
        "python:function:demo.caller",
        "python:function:demo.alpha",
        EdgeConfidence::Ambiguous,
        &["python:function:demo.beta"],
    );

    let matches = call_edges_targeting(
        &conn,
        "python:function:demo.beta",
        EdgeConfidence::Ambiguous,
    )
    .expect("query call edges targeting beta");

    assert_eq!(matches.len(), 1);
    let edge = &matches[0];
    assert_eq!(edge.from_id, "python:function:demo.caller");
    assert_eq!(edge.to_id, "python:function:demo.beta");
    assert_eq!(edge.stored_to_id, "python:function:demo.alpha");
    assert_eq!(edge.confidence, EdgeConfidence::Ambiguous);
}

#[test]
fn call_edges_targeting_dedupes_stored_to_id_also_listed_as_candidate() {
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity(&conn, "python:function:demo.caller", "function");
    insert_entity(&conn, "python:function:demo.alpha", "function");
    insert_entity(&conn, "python:function:demo.beta", "function");
    insert_calls_edge(
        &conn,
        "python:function:demo.caller",
        "python:function:demo.alpha",
        EdgeConfidence::Ambiguous,
        &["python:function:demo.alpha", "python:function:demo.beta"],
    );

    let matches = call_edges_targeting(
        &conn,
        "python:function:demo.alpha",
        EdgeConfidence::Ambiguous,
    )
    .expect("query call edges targeting alpha");

    assert_eq!(matches.len(), 1);
    assert_eq!(matches[0].to_id, "python:function:demo.alpha");
}

#[test]
fn call_edges_from_expands_ambiguous_candidates_once() {
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity(&conn, "python:function:demo.caller", "function");
    insert_entity(&conn, "python:function:demo.alpha", "function");
    insert_entity(&conn, "python:function:demo.beta", "function");
    insert_calls_edge(
        &conn,
        "python:function:demo.caller",
        "python:function:demo.alpha",
        EdgeConfidence::Ambiguous,
        &["python:function:demo.alpha", "python:function:demo.beta"],
    );

    let matches = call_edges_from(
        &conn,
        "python:function:demo.caller",
        EdgeConfidence::Ambiguous,
    )
    .expect("query outgoing call edges");
    let targets: Vec<&str> = matches.iter().map(|edge| edge.to_id.as_str()).collect();

    assert_eq!(
        targets,
        vec!["python:function:demo.alpha", "python:function:demo.beta"]
    );
}

#[test]
fn resolved_confidence_excludes_ambiguous_candidate_expansion() {
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity(&conn, "python:function:demo.caller", "function");
    insert_entity(&conn, "python:function:demo.alpha", "function");
    insert_entity(&conn, "python:function:demo.beta", "function");
    insert_calls_edge(
        &conn,
        "python:function:demo.caller",
        "python:function:demo.alpha",
        EdgeConfidence::Ambiguous,
        &["python:function:demo.beta"],
    );

    let matches =
        call_edges_targeting(&conn, "python:function:demo.beta", EdgeConfidence::Resolved)
            .expect("query resolved-only callers");

    assert!(matches.is_empty());
}

#[test]
fn entity_at_line_uses_innermost_range_then_kind_precedence() {
    let tempdir = tempfile::tempdir().unwrap();
    let source_path = tempdir.path().join("demo.py");
    std::fs::write(
        &source_path,
        "class Demo:\n    def method(self):\n        return 1\n",
    )
    .unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity_with_range(&conn, "python:module:demo", "module", &source_path, 1, 3);
    insert_entity_with_range(&conn, "python:class:demo.Demo", "class", &source_path, 1, 3);
    insert_entity_with_range(
        &conn,
        "python:function:demo.Demo.method",
        "function",
        &source_path,
        2,
        3,
    );

    let entity = entity_at_line(&conn, source_path.to_str().unwrap(), 2)
        .expect("entity_at query")
        .expect("line should match an entity");

    assert_eq!(entity.id, "python:function:demo.Demo.method");
}

#[test]
fn entity_lookup_and_search_cover_id_and_fts_paths() {
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity(&conn, "python:module:demo", "module");
    insert_entity(&conn, "python:function:demo.TokenManager", "function");

    let entity = entity_by_id(&conn, "python:function:demo.TokenManager")
        .expect("lookup by id")
        .expect("entity should exist");
    assert_eq!(entity.kind, "function");

    let fts_results = find_entities(&conn, "TokenManager", 20, 0, None).expect("FTS search");
    assert_eq!(fts_results.len(), 1);
    assert_eq!(fts_results[0].id, "python:function:demo.TokenManager");

    let like_results = find_entities(&conn, "python:function:demo.TokenManager", 20, 0, None)
        .expect("punctuation-heavy ID search");
    assert_eq!(like_results.len(), 1);
    assert_eq!(like_results[0].id, "python:function:demo.TokenManager");
}

#[test]
fn subsystem_of_entity_resolves_module_and_nested_entities() {
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity(&conn, "core:subsystem:abc", "subsystem");
    insert_entity(&conn, "python:module:pkg.mod", "module");
    insert_entity(&conn, "python:class:pkg.mod.Cls", "class");
    insert_entity(&conn, "python:function:pkg.mod.Cls.method", "function");
    insert_in_subsystem_edge(&conn, "python:module:pkg.mod", "core:subsystem:abc");
    insert_contains_edge(&conn, "python:module:pkg.mod", "python:class:pkg.mod.Cls");
    insert_contains_edge(
        &conn,
        "python:class:pkg.mod.Cls",
        "python:function:pkg.mod.Cls.method",
    );

    // A module resolves directly (depth 0).
    let from_module = subsystem_of_entity(&conn, "python:module:pkg.mod")
        .unwrap()
        .expect("module should resolve");
    assert_eq!(from_module.subsystem_id, "core:subsystem:abc");
    assert_eq!(from_module.via_module_id, "python:module:pkg.mod");

    // A method nested module -> class -> function resolves via its module
    // ancestor (exercises the recursive walk past a non-module container).
    let from_method = subsystem_of_entity(&conn, "python:function:pkg.mod.Cls.method")
        .unwrap()
        .expect("nested method should resolve");
    assert_eq!(from_method.subsystem_id, "core:subsystem:abc");
    assert_eq!(from_method.via_module_id, "python:module:pkg.mod");

    // A module not assigned to any subsystem -> None.
    insert_entity(&conn, "python:module:orphan", "module");
    assert!(
        subsystem_of_entity(&conn, "python:module:orphan")
            .unwrap()
            .is_none()
    );

    // An unknown entity id -> None (no error).
    assert!(
        subsystem_of_entity(&conn, "python:function:does.not.exist")
            .unwrap()
            .is_none()
    );
}

#[test]
fn find_entities_kind_filter_constrains_results() {
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    // Two entities sharing a search term but differing in kind, plus a subsystem
    // named after the same package (the realistic "find the subsystem" case).
    insert_entity(&conn, "python:module:demo", "module");
    insert_entity(&conn, "python:function:demo.run", "function");
    insert_entity(&conn, "core:subsystem:demo", "subsystem");

    // Unfiltered: all three "demo" entities match (FTS or LIKE path).
    let all = find_entities(&conn, "demo", 20, 0, None).expect("unfiltered search");
    assert_eq!(all.len(), 3, "{all:?}");

    // kind=subsystem returns only the subsystem entity.
    let subs = find_entities(&conn, "demo", 20, 0, Some("subsystem")).expect("kind=subsystem");
    assert_eq!(subs.len(), 1, "{subs:?}");
    assert_eq!(subs[0].id, "core:subsystem:demo");
    assert_eq!(subs[0].kind, "subsystem");

    // kind=function returns only the function.
    let funcs = find_entities(&conn, "demo", 20, 0, Some("function")).expect("kind=function");
    assert_eq!(funcs.len(), 1, "{funcs:?}");
    assert_eq!(funcs[0].id, "python:function:demo.run");

    // An unknown (but well-formed) kind simply matches nothing.
    let none = find_entities(&conn, "demo", 20, 0, Some("nonesuch")).expect("unknown kind");
    assert!(none.is_empty(), "{none:?}");

    // A blank kind is rejected as a malformed request.
    assert!(find_entities(&conn, "demo", 20, 0, Some("  ")).is_err());
}

#[test]
fn find_entities_kind_filter_applies_on_punctuation_like_path() {
    // The punctuation-heavy ID search takes the LIKE branch (not FTS); the kind
    // filter must apply there too, and bind correctly against the OR-group.
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity(&conn, "python:module:pkg.svc", "module");
    insert_entity(&conn, "python:function:pkg.svc", "function");

    let like_all = find_entities(&conn, "python:module:pkg.svc", 20, 0, None).expect("like search");
    assert_eq!(like_all.len(), 1);

    let like_module = find_entities(&conn, "pkg.svc", 20, 0, Some("module")).expect("like+kind");
    assert!(
        like_module.iter().all(|e| e.kind == "module"),
        "{like_module:?}"
    );
    assert!(
        like_module.iter().any(|e| e.id == "python:module:pkg.svc"),
        "{like_module:?}"
    );
}

#[test]
fn find_entities_rejects_deep_cursor_before_expanding_fetch() {
    // The cursor offset feeds `fetch_cap = offset + limit`, which both recall
    // paths materialise at OFFSET 0 — so an unbounded offset lets a deep or
    // malformed cursor force SQLite to scan and return a huge prefix on every
    // page. A cursor past the documented maximum (100_000) must be rejected
    // rather than expand the fetch.
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity(&conn, "python:function:demo.target", "function");

    // A cursor far past any honest paging is rejected, and the message names the
    // bound so a client can correct course.
    let err = find_entities(&conn, "demo", 20, 5_000_000, None)
        .expect_err("a deep cursor must be rejected, not materialised");
    let msg = err.to_string();
    assert!(
        msg.contains("cursor offset") && msg.contains("100000"),
        "rejection must name the offset and its bound: {msg}"
    );

    // The boundary itself is accepted (off-by-one guard): offset == max paginates
    // normally (here past the single row, so an empty page).
    let at_bound = find_entities(&conn, "demo", 20, 100_000, None)
        .expect("the maximum offset is still a legal page");
    assert!(
        at_bound.is_empty(),
        "offset past the data yields an empty page"
    );

    // And shallow, legitimate pagination is unaffected.
    let first = find_entities(&conn, "demo", 20, 0, None).expect("first page");
    assert_eq!(first.len(), 1);
}

#[test]
fn find_entities_matches_concept_word_in_docstring() {
    // weft-b7ce301e92 (LW-1): a concept word that lives only in docstring prose
    // — not in any entity name — must be discoverable. FTS over name/short_name
    // alone returns empty here ("borrow" is no entity's name); the LIKE content
    // path over the docstring is what makes discovery beat grep at the entry.
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity_with_properties(
        &conn,
        "python:class:loan.LoanPolicy",
        "class",
        "LoanPolicy",
        r#"{"docstring": "Strategy interface: how many days a user may borrow a book."}"#,
    );
    // A decoy with no matching content.
    insert_entity(&conn, "python:class:user.User", "class");

    let hits = find_entities(&conn, "borrow", 20, 0, None).expect("docstring search");
    assert_eq!(hits.len(), 1, "{hits:?}");
    assert_eq!(hits[0].id, "python:class:loan.LoanPolicy");
}

#[test]
fn find_entities_matches_identifier_substring_fts_cannot() {
    // weft-b7ce301e92 (LW-1): the marquee case. The concept word `library` is a
    // substring of the CamelCase identifier `LibraryService`, but FTS matches
    // whole stemmed tokens (`libraryservice`), so `MATCH 'library'` returns
    // nothing. The LIKE substring path is what surfaces the class an agent is
    // actually looking for.
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity_with_properties(
        &conn,
        "python:class:catalog.LibraryService",
        "class",
        "LibraryService",
        "{}",
    );

    let hits = find_entities(&conn, "library", 20, 0, None).expect("identifier-substring search");
    assert!(
        hits.iter()
            .any(|e| e.id == "python:class:catalog.LibraryService"),
        "substring of a CamelCase identifier must be discoverable: {hits:?}"
    );
}

#[test]
fn find_entities_does_not_leak_briefing_blocked_docstring_content() {
    // weft-b7ce301e92 secret-safety invariant (cf. clarion-307668e2be): a
    // docstring withheld by the pre-ingest scanner (briefing_blocked set) must
    // never become matchable via the new content path — otherwise searching for
    // a leaked secret would resurface the very entity the block exists to hide.
    // An identical docstring WITHOUT the block must match (proves the guard, not
    // the absence of indexing, is what suppresses it).
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    insert_entity_with_properties(
        &conn,
        "python:function:secrets.blocked",
        "function",
        "blocked",
        r#"{"docstring": "uniquesecrettoken in here", "briefing_blocked": "secret_present"}"#,
    );
    insert_entity_with_properties(
        &conn,
        "python:function:secrets.visible",
        "function",
        "visible",
        r#"{"docstring": "uniquesecrettoken in here"}"#,
    );

    let hits = find_entities(&conn, "uniquesecrettoken", 20, 0, None).expect("guarded search");
    assert_eq!(hits.len(), 1, "blocked docstring must not match: {hits:?}");
    assert_eq!(hits[0].id, "python:function:secrets.visible");
}

#[test]
fn contained_entity_ids_is_depth_first_cycle_safe_and_capped() {
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    for id in [
        "python:module:demo",
        "python:class:demo.Demo",
        "python:function:demo.Demo.method",
        "python:function:demo.helper",
    ] {
        insert_entity(&conn, id, "function");
    }
    insert_contains_edge(&conn, "python:module:demo", "python:class:demo.Demo");
    insert_contains_edge(
        &conn,
        "python:class:demo.Demo",
        "python:function:demo.Demo.method",
    );
    insert_contains_edge(&conn, "python:module:demo", "python:function:demo.helper");
    insert_contains_edge(
        &conn,
        "python:function:demo.Demo.method",
        "python:module:demo",
    );

    let traversal = contained_entity_ids(&conn, "python:module:demo", 2)
        .expect("contains traversal should complete");

    assert_eq!(
        traversal.entity_ids,
        vec![
            "python:class:demo.Demo".to_owned(),
            "python:function:demo.Demo.method".to_owned(),
        ]
    );
    assert!(traversal.truncated);
}

#[test]
fn child_entity_ids_returns_only_direct_contains_children() {
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);
    for id in [
        "python:module:demo",
        "python:class:demo.Demo",
        "python:function:demo.Demo.method",
    ] {
        insert_entity(&conn, id, "function");
    }
    insert_contains_edge(&conn, "python:module:demo", "python:class:demo.Demo");
    insert_contains_edge(
        &conn,
        "python:class:demo.Demo",
        "python:function:demo.Demo.method",
    );

    let children = child_entity_ids(&conn, "python:module:demo").expect("direct children");

    assert_eq!(children, vec!["python:class:demo.Demo".to_owned()]);
}

#[test]
fn normalize_source_path_accepts_project_relative_paths_and_rejects_escape() {
    let tempdir = tempfile::tempdir().unwrap();
    let source_path = tempdir.path().join("src").join("demo.py");
    std::fs::create_dir_all(source_path.parent().unwrap()).unwrap();
    std::fs::write(&source_path, "def demo():\n    pass\n").unwrap();

    let normalized =
        normalize_source_path(tempdir.path(), "src/demo.py").expect("relative source path");

    assert_eq!(
        normalized,
        source_path.canonicalize().unwrap().to_str().unwrap()
    );
    let escaped = normalize_source_path(tempdir.path(), "../outside.py")
        .expect_err("path escape should be rejected");
    assert!(
        escaped.to_string().contains("invalid source path"),
        "unexpected error: {escaped}"
    );
}

#[test]
fn resolve_file_surfaces_briefing_blocked_reason_from_properties() {
    let tempdir = tempfile::tempdir().expect("temp project root");
    let project_root = tempdir.path();
    let source_path = project_root.join("secret.env");
    std::fs::write(&source_path, "TOKEN=AKIAIOSFODNN7EXAMPLE\n").expect("write source");
    let canonical = source_path.canonicalize().expect("canonical source");

    let conn = open_fresh(&tempdir);
    conn.execute(
        "INSERT INTO entities (
            id, plugin_id, kind, name, short_name, source_file_path,
            source_line_start, source_line_end, properties, content_hash, created_at, updated_at
         ) VALUES (
            'core:file:hash-secret@secret.env', 'core', 'file', 'secret.env', 'secret.env', ?1,
            1, 1, '{\"briefing_blocked\":\"secret_present\"}', 'hash-secret-file',
            '2026-05-19T00:00:00.000Z', '2026-05-19T00:00:00.000Z'
         )",
        params![canonical.display().to_string()],
    )
    .expect("insert briefing-blocked entity");

    let resolved = resolve_file(&conn, project_root, "secret.env", "env")
        .expect("resolve_file")
        .expect("entity is known");

    assert_eq!(
        resolved.briefing_blocked.as_deref(),
        Some("secret_present"),
        "resolve_file must surface briefing_blocked reason so federation read \
         surfaces (HTTP /api/v1/files) can refuse to expose blocked entities"
    );
}

#[test]
fn resolve_file_returns_none_when_no_file_kind_entity_exists() {
    let tempdir = tempfile::tempdir().expect("temp project root");
    let project_root = tempdir.path();
    let source_path = project_root.join("src").join("demo.py");
    std::fs::create_dir_all(source_path.parent().unwrap()).expect("create source dir");
    std::fs::write(&source_path, "def entry():\n    return 1\n").expect("write source");
    let canonical = source_path.canonicalize().expect("canonical source");

    let conn = open_fresh(&tempdir);
    conn.execute(
        "INSERT INTO entities (
            id, plugin_id, kind, name, short_name, source_file_path,
            source_line_start, source_line_end, properties, content_hash, created_at, updated_at
         ) VALUES (
            'python:module:demo', 'python', 'module', 'demo', 'demo', ?1,
            1, 2, '{}', 'hash-demo-module',
            '2026-05-19T00:00:00.000Z', '2026-05-19T00:00:00.000Z'
         )",
        params![canonical.display().to_string()],
    )
    .expect("insert module entity");

    let resolved =
        resolve_file(&conn, project_root, "src/demo.py", "python").expect("resolve_file");

    assert!(
        resolved.is_none(),
        "resolve_file must fail closed instead of synthesizing a file identity from a module row"
    );
}

#[test]
fn resolve_file_returns_none_briefing_blocked_for_clean_entity() {
    let tempdir = tempfile::tempdir().expect("temp project root");
    let project_root = tempdir.path();
    let source_path = project_root.join("src").join("demo.py");
    std::fs::create_dir_all(source_path.parent().unwrap()).expect("create source dir");
    std::fs::write(&source_path, "def entry():\n    return 1\n").expect("write source");
    let canonical = source_path.canonicalize().expect("canonical source");

    let conn = open_fresh(&tempdir);
    conn.execute(
        "INSERT INTO entities (
            id, plugin_id, kind, name, short_name, source_file_path,
            source_line_start, source_line_end, properties, content_hash, created_at, updated_at
         ) VALUES (
            'python:file:demo', 'python', 'file', 'demo.py', 'demo.py', ?1,
            1, 2, '{}', 'hash-demo',
            '2026-05-19T00:00:00.000Z', '2026-05-19T00:00:00.000Z'
         )",
        params![canonical.display().to_string()],
    )
    .expect("insert clean entity");

    let resolved = resolve_file(&conn, project_root, "src/demo.py", "python")
        .expect("resolve_file")
        .expect("entity is known");

    assert_eq!(resolved.canonical_path.as_str(), "src/demo.py");
    assert!(
        !resolved.canonical_path.as_str().starts_with('/')
            && !resolved.canonical_path.as_str().starts_with("./")
            && !resolved.canonical_path.as_str().starts_with("../"),
        "canonical path must be project-relative POSIX: {:?}",
        resolved.canonical_path
    );
    assert!(
        resolved.briefing_blocked.is_none(),
        "clean entity must not surface a briefing_blocked reason; got {:?}",
        resolved.briefing_blocked
    );
}

#[test]
fn resolve_file_deleted_on_disk_but_cataloged_row_resolves() {
    let tempdir = tempfile::tempdir().expect("temp project root");
    let project_root = tempdir.path();
    let source_path = project_root.join("src").join("deleted.py");
    std::fs::create_dir_all(source_path.parent().unwrap()).expect("create source dir");
    std::fs::write(&source_path, "def gone():\n    return 1\n").expect("write source");
    let canonical = source_path.canonicalize().expect("canonical source");

    let conn = open_fresh(&tempdir);
    conn.execute(
        "INSERT INTO entities (
            id, plugin_id, kind, name, short_name, source_file_path,
            source_line_start, source_line_end, properties, content_hash, created_at, updated_at
         ) VALUES (
            'python:file:deleted', 'python', 'file', 'deleted.py', 'deleted.py', ?1,
            1, 2, '{}', 'hash-deleted',
            '2026-05-19T00:00:00.000Z', '2026-05-19T00:00:00.000Z'
         )",
        params![canonical.display().to_string()],
    )
    .expect("insert deleted entity");
    std::fs::remove_file(&source_path).expect("delete source after cataloging");

    let resolved = resolve_file(&conn, project_root, "src/deleted.py", "python")
        .expect("resolve_file should use catalog row without requiring disk file")
        .expect("entity is known");

    assert_eq!(resolved.entity_id, "python:file:deleted");
    assert_eq!(resolved.content_hash, "hash-deleted");
    assert_eq!(resolved.canonical_path.as_str(), "src/deleted.py");
    assert!(
        !resolved.canonical_path.as_str().starts_with('/')
            && !resolved.canonical_path.as_str().starts_with("./")
            && !resolved.canonical_path.as_str().starts_with("../"),
        "canonical path must be project-relative POSIX: {:?}",
        resolved.canonical_path
    );
}

#[test]
fn resolve_file_catalog_entry_returns_missing_hash_without_reading_disk() {
    let tempdir = tempfile::tempdir().expect("temp project root");
    let project_root = tempdir.path();
    let source_path = project_root.join("src").join("missing-hash.py");
    std::fs::create_dir_all(source_path.parent().unwrap()).expect("create source dir");
    std::fs::write(&source_path, "def missing_hash():\n    return 1\n").expect("write source");
    let canonical = source_path.canonicalize().expect("canonical source");

    let conn = open_fresh(&tempdir);
    conn.execute(
        "INSERT INTO entities (
            id, plugin_id, kind, name, short_name, source_file_path,
            source_line_start, source_line_end, properties, content_hash, created_at, updated_at
         ) VALUES (
            'python:file:missing_hash', 'python', 'file', 'missing-hash.py', 'missing-hash.py', ?1,
            1, 2, '{}', NULL,
            '2026-05-19T00:00:00.000Z', '2026-05-19T00:00:00.000Z'
         )",
        params![canonical.display().to_string()],
    )
    .expect("insert file entity without cached hash");
    std::fs::remove_file(&source_path).expect("delete source after cataloging");

    let entry = resolve_file_catalog_entry(&conn, project_root, "src/missing-hash.py", "python")
        .expect("catalog lookup should not read deleted source")
        .expect("entity is known");

    assert_eq!(entry.entity_id, "python:file:missing_hash");
    assert_eq!(entry.content_hash, None);
    assert_eq!(entry.canonical_path.as_str(), "src/missing-hash.py");
    assert_eq!(entry.language, "python");
}

#[test]
#[cfg(unix)]
fn resolve_file_unreadable_hash_failure_propagates() {
    use std::os::unix::fs::PermissionsExt;

    let tempdir = tempfile::tempdir().expect("temp project root");
    let project_root = tempdir.path();
    let source_path = project_root.join("src").join("unreadable.py");
    std::fs::create_dir_all(source_path.parent().unwrap()).expect("create source dir");
    std::fs::write(&source_path, "def unreadable():\n    return 1\n").expect("write source");
    let canonical = source_path.canonicalize().expect("canonical source");

    let conn = open_fresh(&tempdir);
    conn.execute(
        "INSERT INTO entities (
            id, plugin_id, kind, name, short_name, source_file_path,
            source_line_start, source_line_end, properties, content_hash, created_at, updated_at
         ) VALUES (
            'python:file:unreadable', 'python', 'file', 'unreadable.py', 'unreadable.py', ?1,
            1, 2, '{}', NULL,
            '2026-05-19T00:00:00.000Z', '2026-05-19T00:00:00.000Z'
         )",
        params![canonical.display().to_string()],
    )
    .expect("insert unreadable entity");
    let original_permissions = std::fs::metadata(&source_path)
        .expect("source metadata")
        .permissions();
    std::fs::set_permissions(&source_path, std::fs::Permissions::from_mode(0o000))
        .expect("make source unreadable");

    if std::fs::read(&source_path).is_ok() {
        std::fs::set_permissions(&source_path, original_permissions).expect("restore source perms");
        eprintln!("skipping unreadable-file assertion because this runner can read 0o000 files");
        return;
    }

    let result = resolve_file(&conn, project_root, "src/unreadable.py", "python");

    std::fs::set_permissions(&source_path, original_permissions).expect("restore source perms");
    let error = result.expect_err("missing catalog hash must propagate hash fallback read failure");
    assert!(
        error.to_string().contains("io error"),
        "unexpected error: {error}"
    );
}

#[test]
fn resolve_file_does_not_echo_invalid_requested_language_over_catalog_inference() {
    let tempdir = tempfile::tempdir().expect("temp project root");
    let project_root = tempdir.path();
    let source_path = project_root.join("src").join("demo.py");
    std::fs::create_dir_all(source_path.parent().unwrap()).expect("create source dir");
    std::fs::write(&source_path, "def entry():\n    return 1\n").expect("write source");
    let canonical = source_path.canonicalize().expect("canonical source");

    let conn = open_fresh(&tempdir);
    conn.execute(
        "INSERT INTO entities (
            id, plugin_id, kind, name, short_name, source_file_path,
            source_line_start, source_line_end, properties, content_hash, created_at, updated_at
         ) VALUES (
            'python:file:demo-language', 'python', 'file', 'demo.py', 'demo.py', ?1,
            1, 2, '{}', 'hash-demo-language',
            '2026-05-19T00:00:00.000Z', '2026-05-19T00:00:00.000Z'
         )",
        params![canonical.display().to_string()],
    )
    .expect("insert python entity");

    let resolved = resolve_file(&conn, project_root, "src/demo.py", "javascript")
        .expect("resolve_file")
        .expect("entity is known");

    assert_eq!(resolved.language, "python");
}

#[test]
fn resolve_file_prefers_core_extension_inference_over_requested_language() {
    let tempdir = tempfile::tempdir().expect("temp project root");
    let project_root = tempdir.path();
    let source_path = project_root.join("src").join("demo.py");
    std::fs::create_dir_all(source_path.parent().unwrap()).expect("create source dir");
    std::fs::write(&source_path, "def entry():\n    return 1\n").expect("write source");
    let canonical = source_path.canonicalize().expect("canonical source");

    let conn = open_fresh(&tempdir);
    conn.execute(
        "INSERT INTO entities (
            id, plugin_id, kind, name, short_name, source_file_path,
            source_line_start, source_line_end, properties, content_hash, created_at, updated_at
         ) VALUES (
            'core:file:src/demo.py', 'core', 'file', 'demo.py', 'demo.py', ?1,
            1, 2, '{}', 'hash-core-demo',
            '2026-05-19T00:00:00.000Z', '2026-05-19T00:00:00.000Z'
         )",
        params![canonical.display().to_string()],
    )
    .expect("insert core file entity");

    let resolved = resolve_file(&conn, project_root, "src/demo.py", "javascript")
        .expect("resolve_file")
        .expect("entity is known");

    assert_eq!(resolved.language, "python");
}

#[test]
fn entity_briefing_block_reason_parses_property_and_tolerates_garbage() {
    assert_eq!(
        entity_briefing_block_reason(r#"{"briefing_blocked":"secret_present"}"#),
        Some("secret_present".to_owned()),
    );
    assert_eq!(
        entity_briefing_block_reason(r#"{"briefing_blocked":"unscanned_source"}"#),
        Some("unscanned_source".to_owned()),
    );
    // No key.
    assert_eq!(entity_briefing_block_reason("{}"), None);
    assert_eq!(entity_briefing_block_reason(r#"{"other":"x"}"#), None);
    // Wrong type — key present but not a string.
    assert_eq!(
        entity_briefing_block_reason(r#"{"briefing_blocked":42}"#),
        None,
    );
}

#[test]
fn entity_briefing_block_reason_fails_closed_on_malformed_json() {
    // Fail-closed contract (SEC-01): a plugin emitting malformed
    // properties JSON must not be able to silently unblock the entity
    // through the federation read paths. Any parse failure returns
    // Some("malformed_properties_json") so callers treat the row as
    // briefing-blocked.
    let expected = Some("malformed_properties_json".to_owned());
    assert_eq!(entity_briefing_block_reason(""), expected);
    assert_eq!(entity_briefing_block_reason("not json"), expected);
    assert_eq!(entity_briefing_block_reason(r#"{"unterminated"#), expected);
    assert_eq!(entity_briefing_block_reason(r#"{"x":}"#), expected);
    // Valid JSON whose root is not an object still parses and follows the
    // non-malformed path (no `briefing_blocked` key on a JSON `null`/array,
    // so the reason is `None`).
    assert_eq!(entity_briefing_block_reason("null"), None);
    assert_eq!(entity_briefing_block_reason("[]"), None);
}

fn insert_run(conn: &Connection, run_id: &str) {
    conn.execute(
        "INSERT INTO runs (id, started_at, config, stats, status) \
         VALUES (?1, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), '{}', '{}', 'running')",
        params![run_id],
    )
    .expect("insert run");
}

#[allow(clippy::too_many_arguments)]
fn insert_finding(
    conn: &Connection,
    id: &str,
    run_id: &str,
    rule_id: &str,
    kind: &str,
    severity: &str,
    entity_id: &str,
    related_entities: &str,
) {
    conn.execute(
        "INSERT INTO findings (
            id, tool, tool_version, run_id, rule_id, kind, severity, confidence,
            confidence_basis, entity_id, related_entities, message, evidence,
            properties, supports, supported_by, status, created_at, updated_at
         ) VALUES (
            ?1, 'loomweave', '1.0.0', ?2, ?3, ?4, ?5, 0.9,
            'ast_match', ?6, ?7, 'msg', '{}',
            '{}', '[]', '[]', 'open',
            strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
            strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
         )",
        params![
            id,
            run_id,
            rule_id,
            kind,
            severity,
            entity_id,
            related_entities
        ],
    )
    .expect("insert finding");
}

#[test]
fn findings_for_emit_joins_entity_path_and_preserves_nullable_location() {
    let tempdir = tempfile::tempdir().expect("tempdir");
    let conn = open_fresh(&tempdir);

    insert_run(&conn, "run-1");
    // A defect anchored to a function with a source location.
    insert_entity_with_range(
        &conn,
        "python:function:auth.tokens.refresh",
        "function",
        Path::new("src/auth/tokens.py"),
        12,
        20,
    );
    // A fact anchored to a subsystem entity — no source_file_path.
    insert_named_entity(
        &conn,
        "core:subsystem:abcd",
        "subsystem",
        "abcd",
        "abcd",
        None,
    );

    insert_finding(
        &conn,
        "core:finding:run-1:defect",
        "run-1",
        "LMWV-PY-STRUCTURE-001",
        "defect",
        "WARN",
        "python:function:auth.tokens.refresh",
        r#"["python:class:auth.sessions::SessionStore"]"#,
    );
    insert_finding(
        &conn,
        "core:finding:run-1:weak-modularity",
        "run-1",
        "LMWV-FACT-CLUSTERING-WEAK-MODULARITY",
        "fact",
        "INFO",
        "core:subsystem:abcd",
        "[]",
    );

    let rows = findings_for_emit(&conn, "run-1").expect("findings_for_emit");
    assert_eq!(rows.len(), 2, "both findings returned: {rows:?}");

    // Ordered by finding id: "defect" sorts before "weak-modularity".
    let defect = &rows[0];
    assert_eq!(defect.id, "core:finding:run-1:defect");
    assert_eq!(defect.rule_id, "LMWV-PY-STRUCTURE-001");
    assert_eq!(defect.kind, "defect");
    assert_eq!(defect.severity, "WARN");
    assert_eq!(defect.entity_id, "python:function:auth.tokens.refresh");
    assert_eq!(
        defect.source_file_path.as_deref(),
        Some("src/auth/tokens.py")
    );
    assert_eq!(defect.source_line_start, Some(12));
    assert_eq!(defect.source_line_end, Some(20));
    assert_eq!(defect.confidence, Some(0.9));
    assert_eq!(
        defect.related_entities_json,
        r#"["python:class:auth.sessions::SessionStore"]"#
    );

    // The subsystem-anchored fact has no source path — the emitter will skip it.
    let fact = &rows[1];
    assert_eq!(fact.id, "core:finding:run-1:weak-modularity");
    assert_eq!(fact.entity_id, "core:subsystem:abcd");
    assert_eq!(fact.source_file_path, None);
    assert_eq!(fact.source_line_start, None);
}

#[test]
fn findings_for_emit_scopes_to_run_id() {
    let tempdir = tempfile::tempdir().expect("tempdir");
    let conn = open_fresh(&tempdir);

    insert_run(&conn, "run-1");
    insert_run(&conn, "run-2");
    insert_entity_with_range(
        &conn,
        "python:function:demo.f",
        "function",
        Path::new("demo.py"),
        1,
        2,
    );
    insert_finding(
        &conn,
        "f-run1",
        "run-1",
        "LMWV-PY-X",
        "defect",
        "WARN",
        "python:function:demo.f",
        "[]",
    );
    insert_finding(
        &conn,
        "f-run2",
        "run-2",
        "LMWV-PY-X",
        "defect",
        "WARN",
        "python:function:demo.f",
        "[]",
    );

    let rows = findings_for_emit(&conn, "run-1").expect("findings_for_emit");
    assert_eq!(rows.len(), 1);
    assert_eq!(rows[0].id, "f-run1");
}

/// Regression for clarion-8b32ba0d02: emission must not leak the path/line of a
/// `briefing_blocked` entity to Filigree (the read API refuses these; the write
/// direction must match). A finding on a still-blocked secret-bearing file is
/// excluded, while an ordinary finding — and the ADR-013 override audit finding,
/// which rides a *non-blocked* `Overridden` anchor — still emit.
#[test]
fn findings_for_emit_excludes_briefing_blocked_anchors() {
    let tempdir = tempfile::tempdir().expect("tempdir");
    let conn = open_fresh(&tempdir);

    insert_run(&conn, "run-1");

    // A clean function entity — ordinary finding, must emit.
    insert_entity_with_range(
        &conn,
        "python:function:auth.tokens.refresh",
        "function",
        Path::new("src/auth/tokens.py"),
        12,
        20,
    );

    // A secret-scanner anchor for a file the operator OVERRODE
    // (`--allow-unredacted-secrets`): recorded as Overridden, so its anchor
    // carries no briefing_blocked reason. The LMWV-SEC-UNREDACTED-SECRETS-ALLOWED
    // audit finding on it must still reach Filigree (ADR-013 audit trail).
    insert_entity_with_range(
        &conn,
        "core:file:allowed.env",
        "file",
        Path::new("allowed.env"),
        1,
        1,
    );

    // A secret-scanner anchor for a file that is still BLOCKED (no override):
    // briefing_blocked = secret_present. Its finding points at the secret's
    // path/line and must be withheld.
    conn.execute(
        "INSERT INTO entities (
            id, plugin_id, kind, name, short_name, source_file_path,
            source_line_start, source_line_end, properties, content_hash, created_at, updated_at
         ) VALUES (
            'core:file:secret.env', 'core', 'file', 'secret.env', 'secret.env', 'secret.env',
            7, 7, '{\"briefing_blocked\":\"secret_present\"}', 'hash-secret-file',
            strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
         )",
        [],
    )
    .expect("insert briefing-blocked entity");

    insert_finding(
        &conn,
        "core:finding:run-1:a-defect",
        "run-1",
        "LMWV-PY-STRUCTURE-001",
        "defect",
        "WARN",
        "python:function:auth.tokens.refresh",
        "[]",
    );
    insert_finding(
        &conn,
        "core:finding:run-1:b-override-allowed",
        "run-1",
        "LMWV-SEC-UNREDACTED-SECRETS-ALLOWED",
        "defect",
        "ERROR",
        "core:file:allowed.env",
        "[]",
    );
    insert_finding(
        &conn,
        "core:finding:run-1:c-secret-detected",
        "run-1",
        "LMWV-SEC-SECRET-DETECTED",
        "defect",
        "ERROR",
        "core:file:secret.env",
        "[]",
    );

    let rows = findings_for_emit(&conn, "run-1").expect("findings_for_emit");
    let ids: Vec<&str> = rows.iter().map(|row| row.id.as_str()).collect();
    assert_eq!(
        ids,
        vec![
            "core:finding:run-1:a-defect",
            "core:finding:run-1:b-override-allowed",
        ],
        "non-blocked findings (incl. the ADR-013 override audit) emit; the \
         briefing_blocked secret finding is excluded: {rows:?}"
    );
    assert!(
        !ids.contains(&"core:finding:run-1:c-secret-detected"),
        "finding on a briefing_blocked entity must not be emitted"
    );
}

#[test]
fn containing_module_id_walks_up_to_the_nearest_module() {
    let tempdir = tempfile::tempdir().unwrap();
    let conn = open_fresh(&tempdir);

    // module -> class -> method nesting via `contains`.
    insert_entity(&conn, "python:module:pkg.mod", "module");
    insert_entity(&conn, "python:class:pkg.mod.Cls", "class");
    insert_entity(&conn, "python:function:pkg.mod.Cls.method", "function");
    insert_contains_edge(&conn, "python:module:pkg.mod", "python:class:pkg.mod.Cls");
    insert_contains_edge(
        &conn,
        "python:class:pkg.mod.Cls",
        "python:function:pkg.mod.Cls.method",
    );

    // A nested method resolves up through its class to the module.
    assert_eq!(
        containing_module_id(&conn, "python:function:pkg.mod.Cls.method")
            .expect("query")
            .as_deref(),
        Some("python:module:pkg.mod"),
    );
    // A module resolves to itself (depth 0).
    assert_eq!(
        containing_module_id(&conn, "python:module:pkg.mod")
            .expect("query")
            .as_deref(),
        Some("python:module:pkg.mod"),
    );
    // A symbol with no module ancestor returns None.
    insert_entity(&conn, "python:function:orphan", "function");
    assert_eq!(
        containing_module_id(&conn, "python:function:orphan").expect("query"),
        None,
    );
}

#[test]
fn graph_totals_count_all_entities_subsystems_and_edges() {
    let dir = tempfile::tempdir().expect("tempdir");
    let conn = open_fresh(&dir);

    // Three entities — one of which is a subsystem — and two edges.
    insert_entity(&conn, "python:module:a", "module");
    insert_entity(&conn, "python:function:a.f", "function");
    insert_entity(&conn, "core:subsystem:abcd", "subsystem");
    insert_contains_edge(&conn, "python:module:a", "python:function:a.f");
    insert_contains_edge(&conn, "python:module:a", "core:subsystem:abcd");

    // entity_total counts every kind, INCLUDING the subsystem (matching the
    // `project_status` convention); subsystem_total is the subset.
    assert_eq!(entity_total(&conn).expect("entity_total"), 3);
    assert_eq!(subsystem_total(&conn).expect("subsystem_total"), 1);
    assert_eq!(edge_total(&conn).expect("edge_total"), 2);
}

#[test]
fn stored_secret_anchor_reads_the_actual_keyed_entity_not_the_heuristic() {
    // L1 (weft-4165f1ed71): a file with two same-rank module candidates (a Rust
    // inline `mod` block: the file module + an inline submodule share one
    // source_file_path, both rank 0). The in-run `remember_finding_anchors`
    // breaks the tie on EMISSION ORDER (last module wins), while the skip-path
    // heuristic `preferred_finding_anchor_by_file` breaks it on SMALLEST ID. If
    // the prior run anchored its secret finding to the larger-id module (because
    // it was emitted last), the heuristic would re-derive the smaller-id module
    // on the next incremental skip and flip the anchor-keyed finding id, minting
    // a duplicate row. The stored-anchor reader returns the entity the prior run
    // ACTUALLY keyed the finding to, removing the lockstep requirement.
    let dir = tempfile::tempdir().expect("tempdir");
    let conn = open_fresh(&dir);

    let file = "src/lib.rs";
    // Two module entities for one file; ids chosen so the LAST-emitted (what
    // remember_finding_anchors keeps) is NOT the smallest id (what the heuristic
    // would pick). The prior run keyed its finding to the larger-id module.
    insert_named_entity(
        &conn,
        "rust:module:a_outer",
        "module",
        file,
        "a_outer",
        Some(file),
    );
    insert_named_entity(
        &conn,
        "rust:module:z_inner",
        "module",
        file,
        "z_inner",
        Some(file),
    );

    // The heuristic would pick the smallest id (a_outer) — diverging from what
    // the prior run stored.
    let heuristic = preferred_finding_anchor_by_file(&conn).expect("heuristic");
    assert_eq!(
        heuristic.get(file).map(String::as_str),
        Some("rust:module:a_outer")
    );

    // The prior run anchored the secret finding to z_inner (emitted last).
    insert_run(&conn, "run-1");
    insert_finding(
        &conn,
        "core:finding:x",
        "run-1",
        "LMWV-SEC-SECRET-DETECTED",
        "defect",
        "ERROR",
        "rust:module:z_inner",
        "[]",
    );

    let stored = stored_secret_finding_anchor_by_file(&conn).expect("stored");
    assert_eq!(
        stored.get(file).map(String::as_str),
        Some("rust:module:z_inner"),
        "stored anchor must be the entity the prior run actually keyed, not the heuristic re-derivation",
    );

    // A file with no prior secret finding is simply absent — the caller falls
    // back to the heuristic for first-time anchors (nothing to duplicate).
    assert!(!stored.contains_key("src/clean.rs"));
}

#[test]
fn stored_secret_anchor_includes_baseline_only_findings() {
    // A baseline-SUPPRESSED detection emits `LMWV-INFRA-SECRET-BASELINE-MATCH`
    // with NO companion `LMWV-SEC-SECRET-DETECTED` row. The old reader consulted
    // only the secret-detected rule, so such a file had no stored anchor, fell
    // back to the heuristic, and could re-anchor (and mint a duplicate baseline
    // finding) on the next incremental skip when the heuristic's same-rank
    // tie-break diverged from the prior run's anchor. The reader must now read
    // the stored anchor for EVERY pre-ingest secret-scan rule.
    let dir = tempfile::tempdir().expect("tempdir");
    let conn = open_fresh(&dir);

    let file = "src/lib.rs";
    // Two same-rank module candidates for one file (Rust inline `mod` block).
    // The heuristic picks the smallest id (a_outer); the prior run keyed its
    // baseline finding to the larger-id module (z_inner, emitted last).
    insert_named_entity(
        &conn,
        "rust:module:a_outer",
        "module",
        file,
        "a_outer",
        Some(file),
    );
    insert_named_entity(
        &conn,
        "rust:module:z_inner",
        "module",
        file,
        "z_inner",
        Some(file),
    );
    assert_eq!(
        preferred_finding_anchor_by_file(&conn)
            .expect("heuristic")
            .get(file)
            .map(String::as_str),
        Some("rust:module:a_outer"),
        "heuristic would diverge from the stored baseline anchor"
    );

    // The only prior secret-scan finding for this file is a BASELINE MATCH —
    // no secret-detected row exists.
    insert_run(&conn, "run-1");
    insert_finding(
        &conn,
        "core:finding:baseline-x",
        "run-1",
        "LMWV-INFRA-SECRET-BASELINE-MATCH",
        "fact",
        "INFO",
        "rust:module:z_inner",
        "[]",
    );

    let stored = stored_secret_finding_anchor_by_file(&conn).expect("stored");
    assert_eq!(
        stored.get(file).map(String::as_str),
        Some("rust:module:z_inner"),
        "a baseline-only file must still yield its stored anchor (not fall back to the heuristic)",
    );
}
