import { expect, test } from 'vitest'
import { connect, Database } from './promise-default.js'

test('vector-test', async () => {
    const db = await connect(":memory:");
    const v1 = new Array(1024).fill(0).map((_, i) => i);
    const v2 = new Array(1024).fill(0).map((_, i) => 1024 - i);
    const result = await db.prepare(`SELECT
        vector_distance_cos(vector32('${JSON.stringify(v1)}'), vector32('${JSON.stringify(v2)}')) as cosf32,
        vector_distance_cos(vector64('${JSON.stringify(v1)}'), vector64('${JSON.stringify(v2)}')) as cosf64,
        vector_distance_l2(vector32('${JSON.stringify(v1)}'), vector32('${JSON.stringify(v2)}')) as l2f32,
        vector_distance_l2(vector64('${JSON.stringify(v1)}'), vector64('${JSON.stringify(v2)}')) as l2f64
    `).all();
    console.info(result);
})

test('explain', async () => {
    const db = await connect(":memory:");
    const stmt = db.prepare("EXPLAIN SELECT 1");
    expect(stmt.columns()).toEqual([
        {
            "name": "addr",
            "type": "INTEGER",
        },
        {
            "name": "opcode",
            "type": "TEXT",
        },
        {
            "name": "p1",
            "type": "INTEGER",
        },
        {
            "name": "p2",
            "type": "INTEGER",
        },
        {
            "name": "p3",
            "type": "INTEGER",
        },
        {
            "name": "p4",
            "type": "INTEGER",
        },
        {
            "name": "p5",
            "type": "INTEGER",
        },
        {
            "name": "comment",
            "type": "TEXT",
        },
    ].map(x => ({ ...x, column: null, database: null, table: null })));
    expect(await stmt.all()).toEqual([
        {
            "addr": 0,
            "comment": "Start at 3",
            "opcode": "Init",
            "p1": 0,
            "p2": 3,
            "p3": 0,
            "p4": "",
            "p5": 0,
        },
        {
            "addr": 1,
            "comment": "output=r[1]",
            "opcode": "ResultRow",
            "p1": 1,
            "p2": 1,
            "p3": 0,
            "p4": "",
            "p5": 0,
        },
        {
            "addr": 2,
            "comment": "",
            "opcode": "Halt",
            "p1": 0,
            "p2": 0,
            "p3": 0,
            "p4": "",
            "p5": 0,
        },
        {
            "addr": 3,
            "comment": "r[1]=1",
            "opcode": "Integer",
            "p1": 1,
            "p2": 1,
            "p3": 0,
            "p4": "",
            "p5": 0,
        },
        {
            "addr": 4,
            "comment": "",
            "opcode": "Goto",
            "p1": 0,
            "p2": 1,
            "p3": 0,
            "p4": "",
            "p5": 0,
        },
    ]);
})

test('in-memory db', async () => {
    const db = await connect(":memory:");
    await db.exec("CREATE TABLE t(x)");
    await db.exec("INSERT INTO t VALUES (1), (2), (3)");
    const stmt = db.prepare("SELECT * FROM t WHERE x % 2 = ?");
    const rows = await stmt.all([1]);
    expect(rows).toEqual([{ x: 1 }, { x: 3 }]);
})


test('implicit connect', async () => {
    const db = new Database(':memory:');
    const defer = db.prepare("SELECT * FROM t");
    await expect(async () => await defer.all()).rejects.toThrowError(/no such table: t/);
    expect(() => db.prepare("SELECT * FROM t")).toThrowError(/no such table: t/);
    expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }]);
})

test('on-disk db large inserts', async () => {
    const path = `test-${(Math.random() * 10000) | 0}.db`;
    const db1 = await connect(path);
    await db1.prepare("CREATE TABLE t(x)").run();
    await db1.prepare("INSERT INTO t VALUES (randomblob(10 * 4096 + 0))").run();
    await db1.prepare("INSERT INTO t VALUES (randomblob(10 * 4096 + 1))").run();
    await db1.prepare("INSERT INTO t VALUES (randomblob(10 * 4096 + 2))").run();
    const stmt1 = db1.prepare("SELECT length(x) as l FROM t");
    expect(stmt1.columns()).toEqual([{ name: "l", column: null, database: null, table: null, type: null }]);
    const rows1 = await stmt1.all();
    expect(rows1).toEqual([{ l: 10 * 4096 }, { l: 10 * 4096 + 1 }, { l: 10 * 4096 + 2 }]);

    await db1.exec("BEGIN");
    await db1.exec("INSERT INTO t VALUES (1)");
    await db1.exec("ROLLBACK");

    const rows2 = await db1.prepare("SELECT length(x) as l FROM t").all();
    expect(rows2).toEqual([{ l: 10 * 4096 }, { l: 10 * 4096 + 1 }, { l: 10 * 4096 + 2 }]);

    await db1.prepare("PRAGMA wal_checkpoint(TRUNCATE)").run();
})

test('on-disk db', async () => {
    const path = `test-${(Math.random() * 10000) | 0}.db`;
    const db1 = await connect(path);
    await db1.exec("CREATE TABLE t(x)");
    await db1.exec("INSERT INTO t VALUES (1), (2), (3)");
    const stmt1 = db1.prepare("SELECT * FROM t WHERE x % 2 = ?");
    expect(stmt1.columns()).toEqual([{ name: "x", column: null, database: null, table: null, type: null }]);
    const rows1 = await stmt1.all([1]);
    expect(rows1).toEqual([{ x: 1 }, { x: 3 }]);
    stmt1.close();
    await db1.close();

    const db2 = await connect(path);
    const stmt2 = db2.prepare("SELECT * FROM t WHERE x % 2 = ?");
    expect(stmt2.columns()).toEqual([{ name: "x", column: null, database: null, table: null, type: null }]);
    const rows2 = await stmt2.all([1]);
    expect(rows2).toEqual([{ x: 1 }, { x: 3 }]);
    db2.close();
})

// attach is not supported in browser for now
// test('attach', async () => {
//     const path1 = `test-${(Math.random() * 10000) | 0}.db`;
//     const path2 = `test-${(Math.random() * 10000) | 0}.db`;
//     const db1 = await connect(path1);
//     await db1.exec("CREATE TABLE t(x)");
//     await db1.exec("INSERT INTO t VALUES (1), (2), (3)");
//     const db2 = await connect(path2);
//     await db2.exec("CREATE TABLE q(x)");
//     await db2.exec("INSERT INTO q VALUES (4), (5), (6)");

//     await db1.exec(`ATTACH '${path2}' as secondary`);

//     const stmt = db1.prepare("SELECT * FROM t UNION ALL SELECT * FROM secondary.q");
//     expect(stmt.columns()).toEqual([{ name: "x", column: null, database: null, table: null, type: null }]);
//     const rows = await stmt.all([1]);
//     expect(rows).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }, { x: 5 }, { x: 6 }]);
// })

test('blobs', async () => {
    const db = await connect(":memory:");
    const rows = await db.prepare("SELECT x'1020' as x").all();
    expect(rows).toEqual([{ x: new Uint8Array([16, 32]) }])
})


test('example-1', async () => {
    const db = await connect(':memory:');
    await db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');

    const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');
    await insert.run('Alice', 'alice@example.com');
    await insert.run('Bob', 'bob@example.com');

    const users = await db.prepare('SELECT * FROM users').all();
    expect(users).toEqual([
        { id: 1, name: 'Alice', email: 'alice@example.com' },
        { id: 2, name: 'Bob', email: 'bob@example.com' }
    ]);
})

test('example-2', async () => {
    const db = await connect(':memory:');
    await db.exec('CREATE TABLE users (name, email)');
    // Using transactions for atomic operations
    const transaction = db.transaction(async (users) => {
        const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');
        for (const user of users) {
            await insert.run(user.name, user.email);
        }
    });

    // Execute transaction
    await transaction([
        { name: 'Alice', email: 'alice@example.com' },
        { name: 'Bob', email: 'bob@example.com' }
    ]);

    const rows = await db.prepare('SELECT * FROM users').all();
    expect(rows).toEqual([
        { name: 'Alice', email: 'alice@example.com' },
        { name: 'Bob', email: 'bob@example.com' }
    ]);
})