import { CompiledQuery } from "kysely";
import { describe, it, expect, vi } from "vitest";

import { PreviewDODialect } from "../../src/db/do-dialect.js";
import type { PreviewDODialectConfig } from "../../src/db/do-dialect.js";

function createMockStub(queryFn = vi.fn()) {
	return { query: queryFn };
}

function createConfig(queryFn = vi.fn()): PreviewDODialectConfig {
	const stub = createMockStub(queryFn);
	return { getStub: () => stub };
}

describe("PreviewDODialect", () => {
	it("creates a SqliteAdapter", () => {
		const dialect = new PreviewDODialect(createConfig());
		const adapter = dialect.createAdapter();
		expect(adapter.constructor.name).toBe("SqliteAdapter");
	});

	it("creates a SqliteQueryCompiler", () => {
		const dialect = new PreviewDODialect(createConfig());
		const compiler = dialect.createQueryCompiler();
		expect(compiler.constructor.name).toBe("SqliteQueryCompiler");
	});
});

describe("PreviewDODriver", () => {
	it("acquires a connection", async () => {
		const dialect = new PreviewDODialect(createConfig());
		const driver = dialect.createDriver();
		const conn = await driver.acquireConnection();
		expect(conn).toBeDefined();
		expect(conn.executeQuery).toBeTypeOf("function");
	});

	it("transaction methods are no-ops (preview is read-only)", async () => {
		const dialect = new PreviewDODialect(createConfig());
		const driver = dialect.createDriver();
		const conn = await driver.acquireConnection();

		// These should not throw
		await driver.beginTransaction(conn, {});
		await driver.commitTransaction(conn);
		await driver.rollbackTransaction(conn);
	});
});

describe("PreviewDOConnection", () => {
	it("passes sql and parameters to the stub", async () => {
		const queryFn = vi.fn().mockResolvedValue({ rows: [], changes: 0 });
		const dialect = new PreviewDODialect(createConfig(queryFn));
		const driver = dialect.createDriver();
		const conn = await driver.acquireConnection();

		await conn.executeQuery(CompiledQuery.raw("SELECT * FROM users WHERE id = ?", ["abc"]));

		expect(queryFn).toHaveBeenCalledWith("SELECT * FROM users WHERE id = ?", ["abc"]);
	});

	it("returns rows from the stub result", async () => {
		const rows = [
			{ id: "1", name: "Alice" },
			{ id: "2", name: "Bob" },
		];
		const queryFn = vi.fn().mockResolvedValue({ rows, changes: 0 });
		const dialect = new PreviewDODialect(createConfig(queryFn));
		const driver = dialect.createDriver();
		const conn = await driver.acquireConnection();

		const result = await conn.executeQuery(CompiledQuery.raw("SELECT * FROM users"));

		expect(result.rows).toEqual(rows);
	});

	it("converts changes to bigint numAffectedRows", async () => {
		const queryFn = vi.fn().mockResolvedValue({ rows: [], changes: 3 });
		const dialect = new PreviewDODialect(createConfig(queryFn));
		const driver = dialect.createDriver();
		const conn = await driver.acquireConnection();

		const result = await conn.executeQuery(CompiledQuery.raw("UPDATE users SET name = ?", ["x"]));

		expect(result.numAffectedRows).toBe(3n);
	});

	it("sets numAffectedRows to undefined when changes is undefined", async () => {
		const queryFn = vi.fn().mockResolvedValue({ rows: [{ count: 5 }] });
		const dialect = new PreviewDODialect(createConfig(queryFn));
		const driver = dialect.createDriver();
		const conn = await driver.acquireConnection();

		const result = await conn.executeQuery(
			CompiledQuery.raw("SELECT count(*) as count FROM users"),
		);

		expect(result.numAffectedRows).toBeUndefined();
	});

	it("handles zero changes correctly", async () => {
		const queryFn = vi.fn().mockResolvedValue({ rows: [], changes: 0 });
		const dialect = new PreviewDODialect(createConfig(queryFn));
		const driver = dialect.createDriver();
		const conn = await driver.acquireConnection();

		const result = await conn.executeQuery(CompiledQuery.raw("DELETE FROM users WHERE 1=0"));

		expect(result.numAffectedRows).toBe(0n);
	});
});
