//! Batch 1 expressiveness: kind, display_name, enclosing_symbol on SymbolInformation.
use camino::Utf8PathBuf;
use psql_scip::{
    core::{ddl::walk_ddl, schema::SchemaModel},
    emit::emit_index,
    parse::split_and_parse,
};
use scip::types::symbol_information::Kind;

fn manifest_dir() -> Utf8PathBuf {
    let base =
        std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR must be set by cargo test");
    Utf8PathBuf::from(base)
}

fn fixture_sql(name: &str) -> String {
    let path = manifest_dir().join("tests/fixtures").join(name);
    std::fs::read_to_string(path.as_std_path())
        .unwrap_or_else(|_| panic!("fixture {name} must be readable"))
}

fn build_batch1_index() -> scip::types::Index {
    let schema_path = manifest_dir().join("tests/fixtures/schema_for_batch1.sql");
    let schema_content = fixture_sql("schema_for_batch1.sql");
    let (stmts, failures) = split_and_parse(&schema_content).expect("schema must parse");
    let mut model = SchemaModel::default();
    walk_ddl(&stmts, &mut model);

    emit_index(
        &schema_path,
        &schema_content,
        &failures,
        &model,
        &[],
        &manifest_dir(),
    )
}

fn schema_doc(index: &scip::types::Index) -> &scip::types::Document {
    &index.documents[0]
}

fn find_sym<'a>(
    doc: &'a scip::types::Document,
    needle: &str,
) -> &'a scip::types::SymbolInformation {
    doc.symbols
        .iter()
        .find(|s| s.symbol.contains(needle))
        .unwrap_or_else(|| panic!("must find symbol containing '{needle}'"))
}

#[test]
fn table_kind_is_struct() {
    let index = build_batch1_index();
    let doc = schema_doc(&index);
    let si = find_sym(doc, "public/orders.");
    assert_eq!(
        si.kind.enum_value(),
        Ok(Kind::Struct),
        "table must have Kind::Struct"
    );
}

#[test]
fn column_kind_is_field() {
    let index = build_batch1_index();
    let doc = schema_doc(&index);
    let si = find_sym(doc, "public/orders#id");
    assert_eq!(
        si.kind.enum_value(),
        Ok(Kind::Field),
        "column must have Kind::Field"
    );
}

#[test]
fn function_kind_is_function() {
    let index = build_batch1_index();
    let doc = schema_doc(&index);
    let si = find_sym(doc, "count_orders");
    assert_eq!(
        si.kind.enum_value(),
        Ok(Kind::Function),
        "function must have Kind::Function"
    );
}

#[test]
fn view_kind_is_class() {
    let index = build_batch1_index();
    let doc = schema_doc(&index);
    let si = find_sym(doc, "active_orders");
    assert_eq!(
        si.kind.enum_value(),
        Ok(Kind::Class),
        "view must have Kind::Class"
    );
}

#[test]
fn enum_type_kind_is_enum() {
    let index = build_batch1_index();
    let doc = schema_doc(&index);
    let si = find_sym(doc, "item_status");
    assert_eq!(
        si.kind.enum_value(),
        Ok(Kind::Enum),
        "enum type must have Kind::Enum"
    );
}

#[test]
fn composite_type_kind_is_struct() {
    let index = build_batch1_index();
    let doc = schema_doc(&index);
    let si = find_sym(doc, "point_2d");
    assert_eq!(
        si.kind.enum_value(),
        Ok(Kind::Struct),
        "composite type must have Kind::Struct"
    );
}

#[test]
fn table_display_name_is_unqualified() {
    let index = build_batch1_index();
    let doc = schema_doc(&index);
    let si = find_sym(doc, "public/orders.");
    assert_eq!(si.display_name, "orders");
}

#[test]
fn column_display_name_is_column_name() {
    let index = build_batch1_index();
    let doc = schema_doc(&index);
    let si = find_sym(doc, "public/orders#email");
    assert_eq!(si.display_name, "email");
}

#[test]
fn function_display_name_is_unqualified() {
    let index = build_batch1_index();
    let doc = schema_doc(&index);
    let si = find_sym(doc, "count_orders");
    assert_eq!(si.display_name, "count_orders");
}

#[test]
fn view_display_name_is_unqualified() {
    let index = build_batch1_index();
    let doc = schema_doc(&index);
    let si = find_sym(doc, "active_orders");
    assert_eq!(si.display_name, "active_orders");
}

#[test]
fn type_display_name_is_unqualified() {
    let index = build_batch1_index();
    let doc = schema_doc(&index);
    let si = find_sym(doc, "item_status");
    assert_eq!(si.display_name, "item_status");
}

#[test]
fn column_enclosing_symbol_is_parent_table() {
    let index = build_batch1_index();
    let doc = schema_doc(&index);
    let table_si = find_sym(doc, "public/orders.");
    let table_sym = table_si.symbol.clone();
    let col_si = find_sym(doc, "public/orders#id");
    assert_eq!(
        col_si.enclosing_symbol, table_sym,
        "column enclosing_symbol must equal the parent table SCIP symbol"
    );
}

#[test]
fn table_enclosing_symbol_is_empty() {
    let index = build_batch1_index();
    let doc = schema_doc(&index);
    let si = find_sym(doc, "public/orders.");
    assert!(
        si.enclosing_symbol.is_empty(),
        "table enclosing_symbol must be empty, got: '{}'",
        si.enclosing_symbol
    );
}

#[test]
fn function_enclosing_symbol_is_empty() {
    let index = build_batch1_index();
    let doc = schema_doc(&index);
    let si = find_sym(doc, "count_orders");
    assert!(
        si.enclosing_symbol.is_empty(),
        "function enclosing_symbol must be empty, got: '{}'",
        si.enclosing_symbol
    );
}

#[test]
fn view_enclosing_symbol_is_empty() {
    let index = build_batch1_index();
    let doc = schema_doc(&index);
    let si = find_sym(doc, "active_orders");
    assert!(
        si.enclosing_symbol.is_empty(),
        "view enclosing_symbol must be empty, got: '{}'",
        si.enclosing_symbol
    );
}

#[test]
fn type_enclosing_symbol_is_empty() {
    let index = build_batch1_index();
    let doc = schema_doc(&index);
    let si = find_sym(doc, "item_status");
    assert!(
        si.enclosing_symbol.is_empty(),
        "type enclosing_symbol must be empty, got: '{}'",
        si.enclosing_symbol
    );
}
