#[pg_test]
fn collection_alias_routes_search_and_can_retarget() {
    create_alias_search_collection("m12_alias_docs_a", "[1,0]", "1");
    create_alias_search_collection("m12_alias_docs_b", "[0,1]", "2");

    let created = alias_rows(
        "SELECT alias_name, collection_name
           FROM pgcontext.create_collection_alias('m12_alias_live', 'm12_alias_docs_a')",
    );
    assert_eq!(
        created,
        vec![("m12_alias_live".to_owned(), "m12_alias_docs_a".to_owned())]
    );
    assert_eq!(alias_search_source_keys("m12_alias_live", "[1,0]"), vec!["1"]);

    let retargeted = alias_rows(
        "SELECT alias_name, collection_name
           FROM pgcontext.create_collection_alias('m12_alias_live', 'm12_alias_docs_b')",
    );
    assert_eq!(
        retargeted,
        vec![("m12_alias_live".to_owned(), "m12_alias_docs_b".to_owned())]
    );
    assert_eq!(alias_search_source_keys("m12_alias_live", "[0,1]"), vec!["2"]);
}

#[pg_test]
fn collection_aliases_lists_visible_aliases_and_drop_removes_route() {
    create_alias_search_collection("m12_alias_list_docs", "[1,0]", "1");
    Spi::run(
        "SELECT pgcontext.create_collection_alias('m12_alias_list_live', 'm12_alias_list_docs')",
    )
    .expect("alias should be created");

    let aliases = alias_rows(
        "SELECT alias_name, collection_name
           FROM pgcontext.collection_aliases()
          WHERE alias_name = 'm12_alias_list_live'",
    );
    assert_eq!(
        aliases,
        vec![(
            "m12_alias_list_live".to_owned(),
            "m12_alias_list_docs".to_owned(),
        )]
    );
    let dropped = Spi::get_one::<bool>(
        "SELECT pgcontext.drop_collection_alias('m12_alias_list_live')",
    )
    .expect("drop alias query should succeed")
    .expect("drop alias should not return null");
    assert!(dropped);

    let aliases = alias_rows(
        "SELECT alias_name, collection_name
           FROM pgcontext.collection_aliases()
          WHERE alias_name = 'm12_alias_list_live'",
    );
    assert!(aliases.is_empty());
}

#[pg_test]
#[should_panic(expected = "collection already exists for alias name: m12_alias_conflict")]
fn create_collection_alias_rejects_existing_collection_names() {
    create_alias_search_collection("m12_alias_conflict", "[1,0]", "1");
    create_alias_search_collection("m12_alias_conflict_target", "[0,1]", "2");

    Spi::run(
        "SELECT pgcontext.create_collection_alias(
            'm12_alias_conflict',
            'm12_alias_conflict_target'
        )",
    )
    .expect("alias conflict should fail");
}

#[pg_test]
#[should_panic(expected = "collection alias already exists: m12_alias_taken")]
fn create_collection_rejects_existing_alias_names() {
    create_alias_search_collection("m12_alias_taken_target", "[1,0]", "1");
    Spi::run(
        "SELECT pgcontext.create_collection_alias(
            'm12_alias_taken',
            'm12_alias_taken_target'
        )",
    )
    .expect("alias should be created");

    Spi::run(
        "CREATE TABLE public.m12_alias_taken (
             id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
             embedding vector
         )",
    )
    .expect("conflicting source table should be created");
    Spi::run("SELECT pgcontext.create_collection('m12_alias_taken', 'public.m12_alias_taken')")
        .expect("collection name conflict with alias should fail");
}

#[pg_test]
#[should_panic(expected = "collection does not exist: m12_alias_missing")]
fn create_collection_alias_rejects_missing_targets() {
    Spi::run("SELECT pgcontext.create_collection_alias('m12_alias_bad', 'm12_alias_missing')")
        .expect("missing alias target should fail");
}

fn create_alias_search_collection(collection_name: &str, vector: &str, source_key: &str) {
    Spi::run(&format!(
        "CREATE TABLE public.{collection_name} (
             id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
             embedding vector
         )"
    ))
    .expect("alias fixture source table should be created");
    Spi::run(&format!(
        "INSERT INTO public.{collection_name} (id, embedding)
         VALUES ({source_key}, '{vector}'::vector)"
    ))
    .expect("alias fixture source row should be inserted");
    Spi::run(&format!(
        "SELECT pgcontext.create_collection('{collection_name}', 'public.{collection_name}')"
    ))
    .expect("alias fixture collection should be created");
    Spi::run(&format!(
        "SELECT pgcontext.register_vector(
            '{collection_name}',
            'embedding',
            'embedding',
            2,
            'l2'
        )"
    ))
    .expect("alias fixture vector should be registered");
    Spi::run(&format!(
        "SELECT pgcontext.upsert_points('{collection_name}', ARRAY['{source_key}'])"
    ))
    .expect("alias fixture point should be registered");
}

fn alias_search_source_keys(collection_name: &str, vector: &str) -> Vec<String> {
    Spi::connect(|client| {
        let rows = client.select(
            &format!(
                "SELECT source_key
                   FROM pgcontext.search('{collection_name}', '{vector}'::vector, 1)"
            ),
            None,
            &[],
        )?;
        let mut output = Vec::new();
        for row in rows {
            output.push(
                row.get::<String>(1)?
                    .expect("source_key should not be null"),
            );
        }
        Ok::<_, spi::Error>(output)
    })
    .expect("alias search query should succeed")
}

fn alias_rows(sql: &str) -> Vec<(String, String)> {
    Spi::connect(|client| {
        let rows = client.select(sql, None, &[])?;
        let mut output = Vec::new();
        for row in rows {
            output.push((
                row.get::<String>(1)?.expect("alias_name should not be null"),
                row.get::<String>(2)?
                    .expect("collection_name should not be null"),
            ));
        }
        Ok::<_, spi::Error>(output)
    })
    .expect("alias rows query should succeed")
}
