//! Coverage tests for reference walker — direct struct fields not reachable via
//! the `NodeRefIterator` (FK constraints, column types, function return types,
//! function body SQL, trigger targets, policy targets).
use psql_scip::{
    core::{ddl::walk_ddl, resolver::walk_references, schema::SchemaModel},
    parse::split_and_parse,
};

fn fixture_sql(name: &str) -> String {
    let base =
        std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR must be set by cargo test");
    let path = std::path::PathBuf::from(base)
        .join("tests/fixtures")
        .join(name);
    std::fs::read_to_string(&path).unwrap_or_else(|_| panic!("fixture {name} must be readable"))
}

fn schema_model() -> SchemaModel {
    let sql = fixture_sql("schema_simple.sql");
    let (stmts, _) = split_and_parse(&sql).expect("schema_simple.sql must parse");
    let mut model = SchemaModel::default();
    walk_ddl(&stmts, &mut model);
    model
}

fn refs_from_fixture(name: &str) -> Vec<psql_scip::core::resolver::ResolvedRef> {
    let model = schema_model();
    let sql = fixture_sql(name);
    let (stmts, _) = split_and_parse(&sql).expect("fixture must parse");
    walk_references(&stmts, &sql, &model)
}

#[test]
fn fk_constraint_emits_referenced_table() {
    let refs = refs_from_fixture("ref_fk_constraint.sql");
    assert!(
        refs.iter().any(|r| r.symbol == "public/users"),
        "FK REFERENCES public.users must emit a ref; got: {refs:?}"
    );
    assert!(
        refs.iter().any(|r| r.symbol == "public/orders"),
        "FK REFERENCES public.orders must emit a ref; got: {refs:?}"
    );
}

#[test]
fn column_enum_type_emits_type_ref() {
    let refs = refs_from_fixture("ref_column_enum_type.sql");
    assert!(
        refs.iter().any(|r| r.symbol == "public/order_status"),
        "column type public.order_status must emit a ref; got: {refs:?}"
    );
}

#[test]
fn function_return_type_emits_ref() {
    let refs = refs_from_fixture("ref_function_return_type.sql");
    assert!(
        refs.iter().any(|r| r.symbol == "public/orders"),
        "RETURNS public.orders must emit a ref; got: {refs:?}"
    );
}

#[test]
fn function_body_emits_table_ref() {
    let refs = refs_from_fixture("ref_function_return_type.sql");
    // The body `SELECT * FROM public.orders WHERE id = p_id` references orders.
    // The return type also references orders — we just need at least one total
    // and confirm orders appears (regardless of whether from body or return type).
    let orders_refs: Vec<_> = refs
        .iter()
        .filter(|r| r.symbol == "public/orders")
        .collect();
    assert!(
        !orders_refs.is_empty(),
        "function body FROM public.orders must emit a ref; got: {refs:?}"
    );
}

#[test]
fn trigger_emits_target_table_ref() {
    let refs = refs_from_fixture("ref_trigger.sql");
    assert!(
        refs.iter().any(|r| r.symbol == "public/users"),
        "trigger ON public.users must emit a ref; got: {refs:?}"
    );
}

#[test]
fn policy_emits_target_table_ref() {
    let refs = refs_from_fixture("ref_policy.sql");
    assert!(
        refs.iter().any(|r| r.symbol == "public/users"),
        "policy ON public.users must emit a ref; got: {refs:?}"
    );
}

#[test]
fn alter_table_fk_emits_referenced_table() {
    let refs = refs_from_fixture("ref_alter_table_fk.sql");
    assert!(
        refs.iter().any(|r| r.symbol == "public/orders"),
        "ALTER TABLE ADD FK REFERENCES public.orders must emit a ref; got: {refs:?}"
    );
    assert!(
        refs.iter().any(|r| r.symbol == "public/users"),
        "ALTER TABLE ADD FK REFERENCES public.users must emit a ref; got: {refs:?}"
    );
}

#[test]
fn alter_table_fk_does_not_emit_source_table() {
    let refs = refs_from_fixture("ref_alter_table_fk.sql");
    assert!(
        !refs.iter().any(|r| r.symbol == "public/order_items"),
        "source table public/order_items must NOT appear as a ref; got: {refs:?}"
    );
}

#[test]
fn create_index_emits_table_ref() {
    let refs = refs_from_fixture("ref_create_index.sql");
    assert!(
        refs.iter().any(|r| r.symbol == "public/orders"),
        "CREATE INDEX ON public.orders must emit a ref; got: {refs:?}"
    );
}

#[test]
fn create_unique_index_emits_table_ref() {
    let refs = refs_from_fixture("ref_create_index.sql");
    assert!(
        refs.iter().any(|r| r.symbol == "public/users"),
        "CREATE UNIQUE INDEX ON public.users must emit a ref; got: {refs:?}"
    );
}

#[test]
fn no_ref_for_builtin_types() {
    let refs = refs_from_fixture("ref_fk_constraint.sql");
    let builtin_refs: Vec<_> = refs
        .iter()
        .filter(|r| {
            r.symbol.contains("pg_catalog")
                || r.symbol.contains("int4")
                || r.symbol.contains("bigint")
                || r.symbol.contains("int8")
        })
        .collect();
    assert!(
        builtin_refs.is_empty(),
        "built-in types must not produce refs; got: {builtin_refs:?}"
    );
}
