package mysqlite

import (
	"errors"
	"github.com/stretchr/testify/require"
	"testing"
)

func openTestDb(t *testing.T) *Db {
	db := openEmptyTestDb(t)
	db.Query("create table mytable (key text, value text)").MustExec()
	db.Query("insert into mytable(key, value) values ('foo', 'bar')").MustExec()
	return db
}

func TestSimpleQuery(t *testing.T) {
	db := openTestDb(t)
	var count int
	db.Query("select count(*) from mytable").MustScanSingle(&count)
	require.Equal(t, 1, count, "expected empty count")
}

func TestSimpleQueryWithNoResults(t *testing.T) {
	db := openTestDb(t)
	var count int
	err := db.Query("select 1 from mytable where key=999").ScanSingle(&count)
	require.Equal(t, ErrNoRows, err)
	require.True(t, errors.Is(err, ErrNoRows))
}

func TestSimpleQueryWithArgs(t *testing.T) {
	db := openTestDb(t)
	var value string
	db.Query("select value from mytable where key = ?").Bind("foo").MustScanSingle(&value)
	require.Equal(t, "bar", value, "bad value returned")
}

func TestQueryWithTwoRows(t *testing.T) {
	db := openTestDb(t)
	db.Query("insert into mytable(key, value) values ('lorem', 'ipsum')").MustExec()

	rows, err := db.Query("select value from mytable").ScanMulti()
	require.NoError(t, err)
	defer rows.MustFinish()

	require.True(t, rows.MustNext(), "expected first row")
	var value string
	rows.MustScan(&value)
	require.Equal(t, "bar", value, "bad value returned")

	require.True(t, rows.MustNext(), "expected second row")
	rows.MustScan(&value)
	require.Equal(t, "ipsum", value, "bad value returned")

	require.False(t, rows.MustNext(), "expected no more rows")
}

func TestQueryWithRange(t *testing.T) {
	db := openTestDb(t)
	db.Query("insert into mytable(key, value) values ('lorem', 'ipsum')").MustExec()

	var err error
	index := 0
	for row := range db.Query("select value from mytable").Range(&err) {
		var value string
		row.MustScan(&value)
		if index == 0 {
			require.Equal(t, "bar", value)
		} else if index == 1 {
			require.Equal(t, "ipsum", value)
		} else {
			require.FailNow(t, "more rows than expected")
		}
		index++
	}
	require.NoError(t, err)
}

func TestUpdateQuery(t *testing.T) {
	db := openTestDb(t)
	func() {
		tx := db.MustBegin()
		defer tx.MustRollback()
		tx.Query("insert into mytable(key, value) values ('lorem', 'bar')").MustExec()
		value := "ipsum"
		key := "lorem"
		tx.Query("update mytable set value = ? where key = ?").Bind(value, key).MustExec()
		tx.MustCommit()
	}()

	var value string
	db.Query("select value from mytable where key = 'lorem'").MustScanSingle(&value)
	require.Equal(t, "ipsum", value)
}

func TestUpdateQueryWithWrongArguments(t *testing.T) {
	type S struct {
		Field string
	}
	db := openTestDb(t)
	abc := S{
		Field: "ipsum",
	}
	err := db.Query("insert into mytable(key, value) values ('lorem', ?)").Bind(abc).Exec()
	require.Error(t, err)
}

func TestUpdateQueryWithPointerValue(t *testing.T) {
	db := openTestDb(t)
	func() {
		tx := db.MustBegin()
		defer tx.MustRollback()
		tx.Query("insert into mytable(key, value) values ('lorem', 'bar')").MustExec()
		value := "ipsum"
		key := "lorem"
		tx.Query("update mytable set value = ? where key = ?").Bind(&value, key).MustExec()
		tx.MustCommit()
	}()

	var value string
	db.Query("select value from mytable where key = 'lorem'").MustScanSingle(&value)
	require.Equal(t, "ipsum", value)
}

func TestUpdateQueryWithSetPointerValue(t *testing.T) {
	type S struct {
		value *string
	}
	db := openTestDb(t)
	func() {
		tx := db.MustBegin()
		defer tx.MustRollback()
		tx.Query("insert into mytable(key, value) values ('lorem', 'bar')").MustExec()
		s := S{nil}
		key := "lorem"
		tx.Query("update mytable set value = ? where key = ?").Bind(s.value, key).MustExec()
		tx.MustCommit()
	}()

	var value *string
	db.Query("select value from mytable where key = 'lorem'").MustScanSingle(&value)
	require.Equal(t, (*string)(nil), value)
}

func TestUpdateQueryWithNullValue(t *testing.T) {
	db := openTestDb(t)
	func() {
		tx := db.MustBegin()
		defer tx.MustRollback()
		tx.Query("insert into mytable(key, value) values ('lorem', 'bar')").MustExec()
		key := "lorem"
		tx.Query("update mytable set value = ? where key = ?").Bind(nil, key).MustExec()
		tx.MustCommit()
	}()

	var value *string
	db.Query("select value from mytable where key = 'lorem'").MustScanSingle(&value)
	require.Nil(t, value)
}

func TestQueryWithPointerStringArguments(t *testing.T) {
	db := openTestDb(t)
	var result *string
	err := db.Query("select value from mytable where key = 'foo'").ScanSingle(&result)
	require.NoError(t, err)
	require.NotNil(t, result)
	require.Equal(t, "bar", *result)
}

func TestQueryWithPointerStringArgumentsCanSetToNull(t *testing.T) {
	db := openTestDb(t)
	db.Query("update mytable set value=null where key = 'foo'").MustExec()
	myString := "some string"
	var result *string
	result = &myString
	err := db.Query("select value from mytable where key = 'foo'").ScanSingle(&result)
	require.NoError(t, err)
	require.Nil(t, result)
}

func TestDeleteQuery(t *testing.T) {
	db := openTestDb(t)
	db.Query("delete from mytable where key = 'foo'").MustExec()

	var count int
	db.Query("select count(*) from mytable where key = 'foo'").MustScanSingle(&count)
	require.Equal(t, 0, count, "expected row to be deleted")
}

func TestTransactionRollback(t *testing.T) {
	db := openTestDb(t)
	func() {
		tx := db.MustBegin()
		defer tx.MustRollback()
		tx.Query("update mytable set value = 'ipsum' where key = 'foo'").MustExec()
		// Intentionally not committing the transaction
	}()

	var value string
	db.Query("select value from mytable where key = 'foo'").MustScanSingle(&value)
	require.Equal(t, "bar", value, "expected original value after rollback")
}

func TestQueryWithInClause(t *testing.T) {
	db := openTestDb(t)
	// Insert additional test rows
	db.Query("insert into mytable(key, value) values ('key1', 'value1')").MustExec()
	db.Query("insert into mytable(key, value) values ('key2', 'value2')").MustExec()

	// Execute query with IN clause
	args := []string{"foo", "key2"}
	rows, err := db.Query("select key, value from mytable where key in (?, ?)").Bind(args).ScanMulti()
	require.NoError(t, err)
	defer rows.MustFinish()

	// Check results
	results := make(map[string]string)
	for rows.MustNext() {
		var key, value string
		rows.MustScan(&key, &value)
		results[key] = value
	}

	// Verify we got exactly the expected results
	require.Equal(t, 2, len(results), "expected 2 matching rows")
	require.Equal(t, "bar", results["foo"], "unexpected value for key 'foo'")
	require.Equal(t, "value2", results["key2"], "unexpected value for key 'key2'")
}