use iridium_core::{types::Value, Engine};

fn exec(engine: &mut Engine, sql: &str) {
    engine.exec(sql).expect(sql);
}

fn query(engine: &mut Engine, sql: &str) -> iridium_core::QueryResult {
    engine.query(sql).expect(sql)
}

#[test]
fn migration_alter_table_add_column() {
    let mut e = Engine::new();
    exec(
        &mut e,
        "CREATE TABLE dbo.users (id INT PRIMARY KEY, name NVARCHAR(100))",
    );
    exec(&mut e, "INSERT INTO dbo.users VALUES (1, N'Alice')");

    exec(&mut e, "ALTER TABLE dbo.users ADD email NVARCHAR(200) NULL");

    exec(
        &mut e,
        "INSERT INTO dbo.users (id, name, email) VALUES (2, N'Bob', N'bob@test.com')",
    );

    let r = query(&mut e, "SELECT id, name, email FROM dbo.users ORDER BY id");
    assert_eq!(r.rows.len(), 2);
    assert!(r.rows[0][2].is_null());
    assert_eq!(r.rows[1][2].to_string_value(), "bob@test.com");
}

#[test]
fn migration_alter_table_drop_column() {
    let mut e = Engine::new();
    exec(
        &mut e,
        "CREATE TABLE dbo.items (id INT PRIMARY KEY, name NVARCHAR(50), extra INT)",
    );
    exec(&mut e, "INSERT INTO dbo.items VALUES (1, N'Widget', 42)");

    exec(&mut e, "ALTER TABLE dbo.items DROP COLUMN extra");

    let r = query(
        &mut e,
        "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'items' ORDER BY ORDINAL_POSITION",
    );
    let col_names: Vec<String> = r.rows.iter().map(|row| row[0].to_string_value()).collect();
    assert_eq!(col_names, vec!["id", "name"]);
}

#[test]
fn migration_create_index_on_existing_table() {
    let mut e = Engine::new();
    exec(
        &mut e,
        "CREATE TABLE dbo.orders (id INT PRIMARY KEY, customer_id INT, order_date DATE, total DECIMAL(10,2))",
    );
    exec(
        &mut e,
        "INSERT INTO dbo.orders VALUES (1, 100, '2025-01-15', 50.00)",
    );
    exec(
        &mut e,
        "INSERT INTO dbo.orders VALUES (2, 100, '2025-02-20', 75.00)",
    );
    exec(
        &mut e,
        "INSERT INTO dbo.orders VALUES (3, 200, '2025-01-10', 30.00)",
    );

    exec(
        &mut e,
        "CREATE INDEX ix_orders_customer ON dbo.orders (customer_id)",
    );

    let r = query(
        &mut e,
        "SELECT name FROM sys.indexes WHERE object_id = OBJECT_ID('dbo.orders') AND name = 'ix_orders_customer'",
    );
    assert_eq!(r.rows.len(), 1);
}

#[test]
fn migration_add_unique_constraint() {
    let mut e = Engine::new();
    exec(
        &mut e,
        "CREATE TABLE dbo.users (id INT PRIMARY KEY, email NVARCHAR(200))",
    );
    exec(
        &mut e,
        "INSERT INTO dbo.users VALUES (1, N'alice@test.com')",
    );

    exec(
        &mut e,
        "ALTER TABLE dbo.users ADD CONSTRAINT UQ_users_email UNIQUE (email)",
    );

    let err = e.exec("INSERT INTO dbo.users VALUES (2, N'alice@test.com')");
    assert!(
        err.is_err(),
        "UNIQUE constraint should reject duplicate email"
    );
}

#[test]
fn migration_drop_check_constraint() {
    let mut e = Engine::new();
    exec(&mut e, "CREATE TABLE dbo.t (id INT, val INT)");
    exec(
        &mut e,
        "ALTER TABLE dbo.t ADD CONSTRAINT CK_t_val CHECK (val > 0)",
    );

    exec(&mut e, "ALTER TABLE dbo.t DROP CONSTRAINT CK_t_val");

    let r = query(
        &mut e,
        "SELECT name FROM sys.check_constraints WHERE parent_object_id = OBJECT_ID('dbo.t')",
    );
    assert_eq!(r.rows.len(), 0);
}

#[test]
fn migration_add_check_constraint() {
    let mut e = Engine::new();
    exec(
        &mut e,
        "CREATE TABLE dbo.accounts (id INT PRIMARY KEY, balance DECIMAL(10,2))",
    );
    exec(&mut e, "INSERT INTO dbo.accounts VALUES (1, 100.00)");

    exec(
        &mut e,
        "ALTER TABLE dbo.accounts ADD CONSTRAINT CK_balance_positive CHECK (balance >= 0)",
    );

    let err = e.exec("INSERT INTO dbo.accounts VALUES (2, -50.00)");
    assert!(
        err.is_err(),
        "CHECK constraint should reject negative balance"
    );

    exec(&mut e, "INSERT INTO dbo.accounts VALUES (2, 0.00)");
    let r = query(&mut e, "SELECT COUNT(*) FROM dbo.accounts");
    assert_eq!(r.rows[0][0], Value::BigInt(2));
}

#[test]
fn migration_add_primary_key() {
    let mut e = Engine::new();
    exec(&mut e, "CREATE TABLE dbo.temp (id INT, name NVARCHAR(50))");
    exec(&mut e, "INSERT INTO dbo.temp VALUES (1, N'Alice')");
    exec(&mut e, "INSERT INTO dbo.temp VALUES (2, N'Bob')");

    exec(
        &mut e,
        "ALTER TABLE dbo.temp ADD CONSTRAINT PK_temp PRIMARY KEY (id)",
    );

    let r = query(
        &mut e,
        "SELECT name FROM sys.key_constraints WHERE parent_object_id = OBJECT_ID('dbo.temp') AND type_desc = 'PRIMARY_KEY_CONSTRAINT'",
    );
    assert_eq!(r.rows.len(), 1);
    assert_eq!(r.rows[0][0].to_string_value(), "PK_temp");
}

#[test]
fn migration_add_foreign_key() {
    let mut e = Engine::new();
    exec(
        &mut e,
        "CREATE TABLE dbo.categories (id INT PRIMARY KEY, name NVARCHAR(50))",
    );
    exec(
        &mut e,
        "INSERT INTO dbo.categories VALUES (1, N'Electronics')",
    );

    exec(
        &mut e,
        "CREATE TABLE dbo.products (id INT PRIMARY KEY, category_id INT, name NVARCHAR(100))",
    );
    exec(&mut e, "INSERT INTO dbo.products VALUES (1, 1, N'Laptop')");

    exec(
        &mut e,
        "ALTER TABLE dbo.products ADD CONSTRAINT FK_products_categories FOREIGN KEY (category_id) REFERENCES dbo.categories(id)",
    );

    let r = query(
        &mut e,
        "SELECT name FROM sys.foreign_keys WHERE parent_object_id = OBJECT_ID('dbo.products')",
    );
    assert_eq!(r.rows.len(), 1);
    assert_eq!(r.rows[0][0].to_string_value(), "FK_products_categories");
}

#[test]
fn migration_multi_step_schema_evolution() {
    let mut e = Engine::new();

    exec(
        &mut e,
        "CREATE TABLE dbo.employees (id INT PRIMARY KEY, name NVARCHAR(100))",
    );
    exec(
        &mut e,
        "INSERT INTO dbo.employees VALUES (1, N'Alice'), (2, N'Bob')",
    );

    exec(
        &mut e,
        "ALTER TABLE dbo.employees ADD department NVARCHAR(50) NULL",
    );
    exec(
        &mut e,
        "ALTER TABLE dbo.employees ADD salary DECIMAL(10,2) NULL",
    );

    exec(
        &mut e,
        "UPDATE dbo.employees SET department = N'Engineering', salary = 100000 WHERE id = 1",
    );
    exec(
        &mut e,
        "UPDATE dbo.employees SET department = N'Sales', salary = 80000 WHERE id = 2",
    );

    let r = query(
        &mut e,
        "SELECT id, name, department, salary FROM dbo.employees ORDER BY id",
    );
    assert_eq!(r.rows.len(), 2);
    assert_eq!(r.rows[0][2].to_string_value(), "Engineering");
    assert_eq!(r.rows[1][2].to_string_value(), "Sales");
}

#[test]
fn migration_drop_foreign_key() {
    let mut e = Engine::new();
    exec(&mut e, "CREATE TABLE dbo.parent (id INT PRIMARY KEY)");
    exec(
        &mut e,
        "CREATE TABLE dbo.child (id INT PRIMARY KEY, parent_id INT)",
    );
    exec(
        &mut e,
        "ALTER TABLE dbo.child ADD CONSTRAINT FK_child_parent FOREIGN KEY (parent_id) REFERENCES dbo.parent(id)",
    );

    exec(
        &mut e,
        "ALTER TABLE dbo.child DROP CONSTRAINT FK_child_parent",
    );

    let r = query(
        &mut e,
        "SELECT name FROM sys.foreign_keys WHERE parent_object_id = OBJECT_ID('dbo.child')",
    );
    assert_eq!(r.rows.len(), 0);
}
