use mongreldb_core::schema::*;
use mongreldb_core::{AlterColumn, Database, MongrelError, Table, Value};
use tempfile::tempdir;

fn schema(nullable_v: bool) -> Schema {
    let mut v_flags = ColumnFlags::empty();
    if nullable_v {
        v_flags = v_flags.with(ColumnFlags::NULLABLE);
    }
    Schema {
        schema_id: 1,
        columns: vec![
            ColumnDef {
                id: 1,
                name: "id".into(),
                ty: TypeId::Int64,
                flags: ColumnFlags::empty().with(ColumnFlags::PRIMARY_KEY),
                default_value: None,
            },
            ColumnDef {
                id: 2,
                name: "v".into(),
                ty: TypeId::Bytes,
                flags: v_flags,
                default_value: None,
            },
        ],
        indexes: vec![],
        colocation: vec![],
        constraints: Default::default(),
        clustered: false,
    }
}

#[test]
fn table_alter_column_renames_and_persists() {
    let dir = tempdir().unwrap();
    {
        let mut table = Table::create(dir.path(), schema(false), 1).unwrap();
        table
            .put(vec![(1, Value::Int64(1)), (2, Value::Bytes(b"a".to_vec()))])
            .unwrap();
        table.commit().unwrap();

        let altered = table
            .alter_column("v", AlterColumn::rename("value"))
            .unwrap();
        assert_eq!(altered.id, 2);
        assert_eq!(altered.name, "value");
        assert!(table.schema().column("v").is_none());
        assert_eq!(table.schema().column("value").unwrap().id, 2);
    }

    let reopened = Table::open(dir.path()).unwrap();
    assert_eq!(reopened.schema().column("value").unwrap().id, 2);
    assert_eq!(reopened.count(), 1);
}

#[test]
fn database_alter_column_updates_catalog_and_reopens() {
    let dir = tempdir().unwrap();
    {
        let db = Database::create(dir.path()).unwrap();
        db.create_table("t", schema(false)).unwrap();
        db.transaction(|tx| {
            tx.put(
                "t",
                vec![(1, Value::Int64(1)), (2, Value::Bytes(b"a".to_vec()))],
            )
        })
        .unwrap();

        db.alter_column("t", "v", AlterColumn::rename("value"))
            .unwrap();
        assert_eq!(
            db.table("t")
                .unwrap()
                .lock()
                .schema()
                .column("value")
                .unwrap()
                .id,
            2
        );
        assert_eq!(
            db.catalog_snapshot().live("t").unwrap().schema.columns[1].name,
            "value"
        );
    }

    let reopened = Database::open(dir.path()).unwrap();
    let table = reopened.table("t").unwrap();
    let table = table.lock();
    assert_eq!(table.schema().column("value").unwrap().id, 2);
    assert_eq!(table.count(), 1);
}

#[test]
fn set_not_null_rejects_existing_nulls() {
    let dir = tempdir().unwrap();
    let db = Database::create(dir.path()).unwrap();
    db.create_table("t", schema(true)).unwrap();
    db.transaction(|tx| tx.put("t", vec![(1, Value::Int64(1))]))
        .unwrap();

    let flags = db
        .table("t")
        .unwrap()
        .lock()
        .schema()
        .column("v")
        .unwrap()
        .flags
        .without(ColumnFlags::NULLABLE);
    let err = db
        .alter_column("t", "v", AlterColumn::set_flags(flags))
        .unwrap_err();
    assert!(
        matches!(err, MongrelError::InvalidArgument(_)),
        "expected InvalidArgument, got {err:?}"
    );
}

#[test]
fn type_change_on_non_empty_incompatible_column_is_rejected() {
    let dir = tempdir().unwrap();
    let db = Database::create(dir.path()).unwrap();
    db.create_table("t", schema(false)).unwrap();
    db.transaction(|tx| {
        tx.put(
            "t",
            vec![(1, Value::Int64(1)), (2, Value::Bytes(b"a".to_vec()))],
        )
    })
    .unwrap();

    let err = db
        .alter_column("t", "v", AlterColumn::set_type(TypeId::Int64))
        .unwrap_err();
    assert!(
        matches!(err, MongrelError::Schema(_)),
        "expected Schema error, got {err:?}"
    );
}
