package driver_test

import (
	"context"
	"database/sql"
	"fmt"
	"testing"
	"time"

	_ "github.com/chaisql/chai"
	"github.com/stretchr/testify/require"
)

type rowtest struct {
	A int
	B string
	C bool
}

func TestDriver(t *testing.T) {
	db, err := sql.Open("chai", ":memory:")
	require.NoError(t, err)
	defer db.Close()

	res, err := db.Exec("CREATE TABLE test(a INT PRIMARY KEY, b TEXT, c BOOL)")
	require.NoError(t, err)
	n, err := res.RowsAffected()
	require.Error(t, err)
	require.EqualValues(t, 0, n)

	for i := 0; i < 10; i++ {
		_, err = db.Exec("INSERT INTO test (a, b, c) VALUES ($1, $2, $3)", i, fmt.Sprintf("foo%d", i), i%2 == 0)
		require.NoError(t, err)
	}

	t.Run("Wildcard", func(t *testing.T) {
		rows, err := db.Query("SELECT * FROM test")
		require.NoError(t, err)
		defer rows.Close()

		var count int
		var rt rowtest
		for rows.Next() {
			err = rows.Scan(&rt.A, &rt.B, &rt.C)
			require.NoError(t, err)
			require.Equal(t, rowtest{count, fmt.Sprintf("foo%d", count), count%2 == 0}, rt)
			count++
		}

		require.NoError(t, rows.Err())
		require.Equal(t, 10, count)
	})

	t.Run("Multiple columns", func(t *testing.T) {
		rows, err := db.Query("SELECT a, c FROM test")
		require.NoError(t, err)
		defer rows.Close()

		var count int
		var a int
		var c bool
		for rows.Next() {
			err = rows.Scan(&a, &c)
			require.NoError(t, err)
			require.Equal(t, count, a)
			require.Equal(t, count%2 == 0, c)
			count++
		}
		require.NoError(t, rows.Err())
		require.Equal(t, 10, count)
	})

	t.Run("Multiple columns with ORDER BY", func(t *testing.T) {
		rows, err := db.Query("SELECT a, c FROM test ORDER BY a")
		require.NoError(t, err)
		defer rows.Close()

		var count int
		var a int
		var c bool
		for rows.Next() {
			err = rows.Scan(&a, &c)
			require.NoError(t, err)
			require.Equal(t, count, a)
			require.Equal(t, count%2 == 0, c)
			count++
		}
		require.NoError(t, rows.Err())
		require.Equal(t, 10, count)
	})

	t.Run("Wilcards with ORDER BY", func(t *testing.T) {
		rows, err := db.Query("SELECT * FROM test ORDER BY a")
		require.NoError(t, err)
		defer rows.Close()

		var count int
		var rt rowtest
		for rows.Next() {
			err = rows.Scan(&rt.A, &rt.B, &rt.C)
			require.NoError(t, err)
			require.Equal(t, rowtest{count, fmt.Sprintf("foo%d", count), count%2 == 0}, rt)
			count++
		}
		require.NoError(t, rows.Err())
		require.Equal(t, 10, count)
	})

	t.Run("Wilcards with ORDER BY and LIMIT", func(t *testing.T) {
		rows, err := db.Query("SELECT * FROM test ORDER BY a LIMIT 5")
		require.NoError(t, err)
		defer rows.Close()

		var count int
		var rt rowtest
		for rows.Next() {
			err = rows.Scan(&rt.A, &rt.B, &rt.C)
			require.NoError(t, err)
			require.Equal(t, rowtest{count, fmt.Sprintf("foo%d", count), count%2 == 0}, rt)
			count++
		}
		require.NoError(t, rows.Err())
		require.Equal(t, 5, count)
	})

	t.Run("Multiple columns and wildcards", func(t *testing.T) {
		rows, err := db.Query("SELECT a, a, *, b, c, * FROM test")
		require.NoError(t, err)
		defer rows.Close()

		var count int
		var a int
		var aa int
		var b string
		var c bool
		var dt1, dt2 rowtest
		for rows.Next() {
			err = rows.Scan(&a, &aa, &dt1.A, &dt1.B, &dt1.C, &b, &c, &dt2.A, &dt2.B, &dt2.C)
			require.NoError(t, err)
			require.Equal(t, count, a)
			require.Equal(t, fmt.Sprintf("foo%d", count), b)

			require.Equal(t, count%2 == 0, c)
			require.Equal(t, rowtest{count, fmt.Sprintf("foo%d", count), count%2 == 0}, dt1)
			require.Equal(t, rowtest{count, fmt.Sprintf("foo%d", count), count%2 == 0}, dt2)
			count++
		}
		require.NoError(t, rows.Err())
		require.Equal(t, 10, count)
	})

	t.Run("Params", func(t *testing.T) {
		rows, err := db.Query("SELECT a FROM test WHERE a = $1", 5)
		require.NoError(t, err)
		defer rows.Close()

		var count int
		var a int
		for rows.Next() {
			err = rows.Scan(&a)
			require.NoError(t, err)
			require.Equal(t, 5, a)
			count++
		}
		require.NoError(t, rows.Err())
		require.Equal(t, 1, count)
	})

	t.Run("Transactions", func(t *testing.T) {
		tx, err := db.Begin()
		require.NoError(t, err)
		defer func() { _ = tx.Rollback() }()

		rows, err := tx.Query("SELECT * FROM test")
		require.NoError(t, err)
		defer rows.Close()

		var count int
		var rt rowtest
		for rows.Next() {
			err = rows.Scan(&rt.A, &rt.B, &rt.C)
			require.NoError(t, err)
			require.Equal(t, rowtest{count, fmt.Sprintf("foo%d", count), count%2 == 0}, rt)
			count++
		}
		require.NoError(t, rows.Err())
		require.Equal(t, 10, count)
	})

	t.Run("Multiple queries", func(t *testing.T) {
		rows, err := db.Query(`
			SELECT * FROM test;;;
			INSERT INTO test (a, b, c) VALUES (10, 'foo10', true);
			SELECT * FROM test;
		`)
		require.NoError(t, err)
		defer rows.Close()

		var count int
		var rt rowtest
		for rows.Next() {
			err = rows.Scan(&rt.A, &rt.B, &rt.C)
			require.NoError(t, err)
			require.Equal(t, rowtest{count, fmt.Sprintf("foo%d", count), count%2 == 0}, rt)
			count++
		}
		require.NoError(t, rows.Err())
		require.Equal(t, 11, count)
	})

	t.Run("Multiple queries in transaction", func(t *testing.T) {
		tx, err := db.Begin()
		require.NoError(t, err)
		defer func() { _ = tx.Rollback() }()

		rows, err := tx.Query(`
			SELECT * FROM test;;;
			INSERT INTO test (a, b, c) VALUES (11, 'foo11', false);
			SELECT * FROM test;
		`)
		require.NoError(t, err)
		defer rows.Close()

		var count int
		var rt rowtest
		for rows.Next() {
			err = rows.Scan(&rt.A, &rt.B, &rt.C)
			require.NoError(t, err)
			require.Equal(t, rowtest{count, fmt.Sprintf("foo%d", count), count%2 == 0}, rt)
			count++
		}
		require.NoError(t, rows.Err())
		require.Equal(t, 12, count)
	})

	t.Run("Multiple queries in read only transaction", func(t *testing.T) {
		tx, err := db.BeginTx(context.Background(), &sql.TxOptions{ReadOnly: true})
		require.NoError(t, err)
		defer func() { _ = tx.Rollback() }()

		_, err = tx.Query(`
			SELECT * FROM test;;;
			INSERT INTO test (a, b, c) VALUES (12, 13, 14);
			SELECT * FROM test;
		`)
		require.EqualError(t, err, "cannot put in read-only mode")
	})
}

func TestPreparedStatement(t *testing.T) {
	db, err := sql.Open("chai", ":memory:")
	require.NoError(t, err)
	defer db.Close()

	_, err = db.Exec(`
		CREATE TABLE bar(a INT PRIMARY KEY);
	`)
	require.NoError(t, err)

	stmt, err := db.Prepare("INSERT INTO bar (a) VALUES ($1)")
	require.NoError(t, err)
	defer stmt.Close()

	for i := 0; i < 12; i++ {
		_, err = stmt.Exec(i)
		require.NoError(t, err)
	}

	err = stmt.Close()
	require.NoError(t, err)

	stmt, err = db.Prepare("SELECT * FROM bar")
	require.NoError(t, err)

	rows, err := stmt.Query()
	require.NoError(t, err)
	defer rows.Close()

	var count int
	var a int
	for rows.Next() {
		err = rows.Scan(&a)
		require.NoError(t, err)
		require.Equal(t, count, a)
		count++
	}
	require.NoError(t, rows.Err())
	require.Equal(t, 12, count)
}

func TestDriverWithTimeValues(t *testing.T) {
	db, err := sql.Open("chai", ":memory:")
	require.NoError(t, err)
	defer db.Close()

	now := time.Now().UTC().Truncate(time.Microsecond)
	_, err = db.Exec("CREATE TABLE test(a TIMESTAMP PRIMARY KEY); INSERT INTO test (a) VALUES ($1)", now)
	require.NoError(t, err)

	tx, err := db.BeginTx(context.Background(), &sql.TxOptions{ReadOnly: true})
	require.NoError(t, err)
	defer func() { _ = tx.Rollback() }()

	var tt time.Time
	err = tx.QueryRow(`SELECT a FROM test`).Scan(&tt)
	require.NoError(t, err)
	require.Equal(t, now, tt)
}
