import { test, expect, beforeAll, afterAll } from "bun:test"
import assert from "assert"
import { openDB } from "../src/db"

// Get PlanetScale credentials from environment
const DATABASE_HOST = process.env.DATABASE_HOST
const DATABASE_USERNAME = process.env.DATABASE_USERNAME
const DATABASE_PASSWORD = process.env.DATABASE_PASSWORD
const DATABASE_NAME = process.env.DATABASE_NAME

let HAS_PLANETSCALE = DATABASE_HOST && DATABASE_USERNAME && DATABASE_PASSWORD && DATABASE_NAME

if (HAS_PLANETSCALE) {
  await runTests()
} else {
  console.error("PlanetScale credentials not found in environment variables.\nPlease set DATABASE_HOST, DATABASE_USERNAME, DATABASE_PASSWORD, and DATABASE_NAME")
}

async function runTests() {
  let db: Awaited<ReturnType<typeof openDB>>

  beforeAll(async () => {
    // Connect to PlanetScale test database
    db = await openDB({
      planetscale: {
        host: DATABASE_HOST,
        username: DATABASE_USERNAME,
        password: DATABASE_PASSWORD,
      }
    })

    // Set up test tables
    await db.run(`
    CREATE TABLE IF NOT EXISTS users (
      id BIGINT AUTO_INCREMENT PRIMARY KEY,
      name VARCHAR(255) NOT NULL
    )
  `)
  })

  afterAll(async () => {
    // Clean up test data
    await db.run("DROP TABLE IF EXISTS users")
  })

  type User = {
    id: number
    name: string
  }

  test("basic queries", async () => {
    // Insert test data
    await db.run(
      "INSERT INTO users (name) VALUES ($name)",
      { name: "John" }
    )

    // Test single row query
    const user = await db.queryOne<User>("SELECT * FROM users WHERE name = $name", { name: "John" })
    assert(user !== null, "User should be defined")
    assert.equal(user.name, "John")

    // Test multiple rows
    await db.run("INSERT INTO users (name) VALUES ($name)", { name: "Jane" })
    const users = await db.query<User>("SELECT * FROM users ORDER BY name")
    assert.equal(users.length, 2)
    assert.equal(users[0].name, "Jane")
    assert.equal(users[1].name, "John")
  })

  test("parameter conversion", async () => {
    // Test multiple parameters
    const users = await db.query<User>(
      "SELECT * FROM users WHERE name LIKE $search AND id > $minId",
      { search: "J%", minId: 0 }
    )
    assert(users.length > 0, "Should find users matching pattern")
    users.forEach(user => {
      assert(user.name.startsWith("J"), "Each user name should start with J")
    })
  })

  test("row mapping", async () => {
    class UserModel {
      constructor(private data: { id: number, name: string }) { }

      displayName() {
        return `User ${this.data.name}`
      }
    }

    const user = await db.queryOne<User, UserModel>(
      "SELECT * FROM users WHERE name = $name",
      { name: "John" },
      row => new UserModel(row)
    )

    assert(user !== null, "User should be defined")
    assert.equal(user.displayName(), "User John")
  })

  test("no results handling", async () => {
    const nonExistentUser = await db.queryOne<User>(
      "SELECT * FROM users WHERE name = $name",
      { name: "NonExistent" }
    )
    assert.equal(nonExistentUser, null)

    const noUsers = await db.query<User>(
      "SELECT * FROM users WHERE name = $name",
      { name: "NonExistent" }
    )
    assert.equal(noUsers.length, 0)
  })

  test("transactions", async () => {
    await db.run("CREATE TABLE IF NOT EXISTS messages (message TEXT)")
    await db.run("DROP TABLE IF EXISTS messages")
    await db.run("CREATE TABLE IF NOT EXISTS messages (message TEXT)")

    try {
      await db.transaction(async (tx) => {
        await tx.run("INSERT INTO messages (message) VALUES ($message)", { message: 'hey there' })
        const result = await tx.queryOne<{ message: string }>("SELECT message FROM messages")
        assert.equal(result?.message, "hey there")
        throw new Error("Kill the transaction")
      })
    } catch (e) {
      // console.error(e)
    }

    const result = await db.queryOne<{ message: string }>("SELECT message FROM messages")
    assert.equal(result, null)

    // Clean up
    await db.run("DROP TABLE IF EXISTS messages")
  })
}