import { test, beforeAll, afterAll } from "bun:test"
import assert from 'assert'
import { rm } from "fs/promises"

import { openDB } from "../src/db.ts"

function xtest(...args: any) { }

class Item {
    id!: number
    name!: string
    value?: number
}

interface Person {
    id: number
    name: string
    age: number
}

class PersonModel {
    get id() {
        return this.person.id
    }

    constructor(private person: Person) { }

    displayName() {
        return `${this.person.name} (${this.person.age})`
    }
}

const TEST_DBNAME = "test.datamapper.db"

const newDB = async () =>
    (await openDB({ sqlite: TEST_DBNAME }))
        .model(Item, 'items', { table: 'not_items' })
        .model(PersonModel, 'people')

let db = await newDB()

beforeAll(async () => {
    await rm(TEST_DBNAME).catch(() => { })  // Clean up any existing db
    db = await newDB()
    await db.run("CREATE TABLE IF NOT EXISTS not_items (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, value INTEGER)")
})

afterAll(async () => {
    await db.run("DROP TABLE IF EXISTS not_items") // Drop table
    await rm(TEST_DBNAME)
})

test("DataMapper setup and basic operations", async () => {
    await db.run("DELETE FROM not_items") // Clear table before tests

    // Create
    const item = await db.items.create({ name: "Test Item 1", value: 100 })
    assert.ok(item.id, "Created model should have an ID")
    assert.equal(item.name, "Test Item 1")
    assert.equal(item.value, 100)

    // Find by ID
    const found = await db.items.find(item.id)
    assert.ok(found, "Should find the created model by ID")
    assert.equal(found?.id, item.id)
    assert.equal(found?.name, "Test Item 1")
    assert.equal(found?.value, 100)

    // Update
    const updated = await db.items.update(item.id, { name: "Updated Test Item 1", value: 101 })
    assert.ok(updated, "Update operation should return the updated model")
    assert.equal(updated?.name, "Updated Test Item 1")
    assert.equal(updated?.value, 101)

    const verifiedUpdate = await db.items.find(item.id)
    assert.equal(verifiedUpdate?.name, "Updated Test Item 1", "Model name should be updated in DB")
    assert.equal(verifiedUpdate?.value, 101, "Model value should be updated in DB")

    // Create another item for 'all' and 'one' tests
    await db.items.create({ name: "Test Item 2", value: 200 })

    // All
    const all = await db.items.all()
    assert.equal(all.length, 2, "Should retrieve all models")
    assert.ok(all.find(m => m.name === "Updated Test Item 1"), "All models should include the updated first item")
    assert.ok(all.find(m => m.name === "Test Item 2"), "All models should include the second item")

    // One by specific criteria
    const oneByName = await db.items.one("name = $name", { name: "Test Item 2" })
    assert.ok(oneByName, "Should find one model by name")
    assert.equal(oneByName?.name, "Test Item 2")
    assert.equal(oneByName?.value, 200)

    // One with no criteria (should pick first or use LIMIT 1)
    const first = await db.items.one()
    assert.ok(first, "Should retrieve one model with no specific criteria")

    // The order isn't guaranteed without ORDER BY, so we check if it's one of the existing models
    const firstExists = all.some(m => m.id === first.id && m.name === first.name)
    assert.ok(firstExists, "The model retrieved by one() should be one of the existing models")

    // Delete
    const toDelete = item.id
    const deleteResult = await db.items.delete(toDelete)
    assert.ok(deleteResult, "Delete operation should be successful")

    const deleted = await db.items.find(toDelete)
    assert.equal(deleted, null, "Deleted model should not be found")

    const remaining = await db.items.all()
    assert.equal(remaining.length, 1, "Should have one model remaining after deletion")
    assert.equal(remaining[0]?.name, "Test Item 2")
})

test("DataMapper one with object query", async () => {
    await db.run("DELETE FROM not_items") // Clear table before tests

    // Create test items
    const item1 = await db.items.create({ name: "Jon", value: 30 })
    const item2 = await db.items.create({ name: "Jon", value: 40 })
    const item3 = await db.items.create({ name: "Jane", value: 30 })

    // Find by single property
    const jonItems = await db.items.one({ name: "Jon" })
    assert.ok(jonItems, "Should find at least one item with name Jon")
    assert.equal(jonItems?.name, "Jon", "Should find an item with the name Jon")

    // Find by single property
    const jons = await db.items.all({ name: "Jon" })
    assert.equal(jons.length, 2, "Should find both items with the name Jon")

    // Find by multiple properties (narrowing down)
    const jon30 = await db.items.one({ name: "Jon", value: 30 })
    assert.ok(jon30, "Should find the specific item with name Jon and value 30")
    assert.equal(jon30?.id, item1.id, "Should match the first item created")
    assert.equal(jon30?.name, "Jon")
    assert.equal(jon30?.value, 30)

    // Find with property that doesn't exist
    const nonExistent = await db.items.one({ name: "Nobody" })
    assert.equal(nonExistent, null, "Should return null when no item matches the query")

    const all = await db.items.all(`name LIKE $name`, { name: `%Jon%`, limit: 1 })
    assert.equal(all.length, 1, "Should find 1 item with the name Jon")
})

test("DataMapper with query and queryOne", async () => {
    await db.run("DELETE FROM not_items") // Clear table before tests

    // Create test items
    const item1 = await db.items.create({ name: "Jon", value: 30 })
    const item2 = await db.items.create({ name: "Ron", value: 40 })

    const all = await db.items.query("SELECT * FROM not_items")
    assert.equal(all.length, 2, "Should find 2 items")
    assert.equal(all[0].name, "Jon")
    assert.equal(all[1].name, "Ron")

    const one = await db.items.queryOne("SELECT * FROM not_items WHERE name = $name", { name: "Jon" })
    assert.equal(one?.name, "Jon")
})