package bulk

import (
	"code.justin.tv/d8a/iceman/lib/queries"
	"code.justin.tv/d8a/iceman/test_common"
	"database/sql"
	"fmt"
	_ "github.com/mattn/go-sqlite3"
	"github.com/stretchr/testify/require"
	"math/rand"
	"testing"
	"time"
)

var (
	db            *sql.DB
	rows          *sql.Rows
	err           error
	baddies       int
	dbType        string
	testDB        test_common.TestDB
	driverQueries queries.DriverQueries
)

func init() {
	dbType, testDB = test_common.ProcessDBTestFlags()
}

func createTable() string {
	return `CREATE TABLE IF NOT EXISTS artists (
                id INTEGER PRIMARY KEY,
                name VARCHAR(255) NOT NULL
            );`
}

func insertRow(driverQueries queries.DriverQueries) string {

	pmarkers := "(" + driverQueries.CreatePlaceholders(2) + ")"
	return "INSERT INTO artists (id, name) VALUES " + pmarkers
}

// setup db and tables
// returns total number of entries that will be deleted
func setupDB(t *testing.T) {

	fmt.Printf("Testing on the \"%v\" database.\n", dbType)

	var err error
	db, driverQueries, err = testDB.SetupDB(t)
	require.Nil(t, err)

	// create our test table
	fmt.Print("Creating test table...")
	if _, err = db.Exec(createTable()); err != nil {
		require.Nil(t, err)
	}

	baddies = 0

	names := [4]string{"Picasso", "Michelangelo", "Beyonce", "Kanye"}

	rand.Seed(time.Now().UTC().UnixNano())

	for i := 1; i <= 1000; i++ {
		name := names[rand.Intn(len(names))]
		if name == "Michelangelo" || name == "Picasso" {
			baddies++
		}
		_, err = db.Exec(insertRow(driverQueries), i, name)
		require.Nil(t, err)
	}
	fmt.Println("DONE.")

}

// test single bulk operation with cap writes
func TestSingleBulk(t *testing.T) {
	for i := 1; i <= 3; i++ {
		func() {
			fmt.Printf("TEST %v of 3\n\n", i)
			// initialize db
			setupDB(t)
			defer testDB.CleanupDB()

			fmt.Printf("How many to delete: %v\n\n", baddies)

			fmt.Print("Executing bulk...")

			done := false

			for !done {
				fileBulk, err := GetBulkFromDir("test_bulk_" + dbType)
				require.Nil(t, err)
				require.NotEmpty(t, fileBulk)

				err = RunSingleBulk(db, driverQueries, fileBulk[0])

				require.Nil(t, err)

				row := db.QueryRow(`SELECT complete FROM iceman_bulk LIMIT 1`)
				err = row.Scan(&done)
				if err != sql.ErrNoRows {
					require.Nil(t, err)
				}
			}

			fmt.Println("DONE")
		}()
	}
}

// test bulk operation with cap writes
func TestBulk(t *testing.T) {
	for i := 1; i <= 3; i++ {

		// Anonymous function for defer scope
		func() {
			fmt.Printf("TEST %v of 3\n\n", i)
			// initialize db
			setupDB(t)
			defer testDB.CleanupDB()

			fmt.Printf("How many to delete: %v\n\n", baddies)

			fmt.Print("Executing bulk...")

			done := false

			for !done {
				err = RunBulk(db, driverQueries, "test_bulk_"+dbType)
				require.Nil(t, err, "Error that shouldn't be here")

				rows, err = db.Query("SELECT complete FROM iceman_bulk ORDER BY executed_at DESC;")
				require.Nil(t, err)

				completes := make([]bool, 0)

				for rows.Next() {
					var complete bool
					err = rows.Scan(&complete)
					require.Nil(t, err)

					completes = append(completes, complete)
				}
				rows.Close()

				done = completes[0] && completes[1]
			}

			fmt.Println("DONE.")

			fmt.Print("Checking for correctness...")

			rows, err = db.Query("SELECT name, complete, next_row FROM iceman_bulk ORDER BY executed_at DESC;")
			require.Nil(t, err)

			names := make([]string, 0)
			completes := make([]bool, 0)
			nextRows := make([]int, 0)

			for rows.Next() {
				var name string
				var complete bool
				var next_row int

				err = rows.Scan(&name, &complete, &next_row)
				require.Nil(t, err)

				names = append(names, name)
				completes = append(completes, complete)
				nextRows = append(nextRows, next_row)
			}
			rows.Close()

			// checking names
			require.Equal(t, []string{"Del", "Prep"}, names, "names are incorrect")

			// checking completes
			require.Equal(t, []bool{true, true}, completes, "should both be true")

			// checking nextRows
			require.Equal(t, []int{1001, 1001}, nextRows, "should both be 1001")

			rows, err = db.Query("SELECT COUNT(*) FROM artists;")
			require.Nil(t, err)

			var count int
			for rows.Next() {
				err = rows.Scan(&count)
				require.Nil(t, err)
			}
			rows.Close()

			// check to see if we deleted all the entries we wanted to remove
			require.Equal(t, baddies, 1000-count, "bulk operations not executed correctly")

			fmt.Println("DONE.\n ")
			fmt.Println("--------------------------------")
		}()
	}
}

// test bulk operation with cap writes
func TestBulkNoLimit(t *testing.T) {
	for i := 1; i <= 3; i++ {

		// Anonymous function for defer scope
		func() {
			fmt.Printf("TEST %v of 3\n\n", i)
			// initialize db
			setupDB(t)
			defer testDB.CleanupDB()

			fmt.Printf("How many to delete: %v\n\n", baddies)

			fmt.Print("Executing bulk...")

			done := false

			for !done {
				err = RunBulk(db, driverQueries, "test_bulk_no_limit_"+dbType)
				require.Nil(t, err)

				rows, err = db.Query("SELECT complete FROM iceman_bulk ORDER BY executed_at DESC;")
				require.Nil(t, err)

				completes := make([]bool, 0)

				for rows.Next() {
					var complete bool
					err = rows.Scan(&complete)
					require.Nil(t, err)

					completes = append(completes, complete)
				}
				rows.Close()

				done = completes[0] && completes[1]
			}

			fmt.Println("DONE.")

			fmt.Print("Checking for correctness...")

			rows, err = db.Query("SELECT name, complete, next_row FROM iceman_bulk ORDER BY executed_at DESC;")
			require.Nil(t, err)

			names := make([]string, 0)
			completes := make([]bool, 0)
			nextRows := make([]int, 0)

			for rows.Next() {
				var name string
				var complete bool
				var next_row int

				err = rows.Scan(&name, &complete, &next_row)
				require.Nil(t, err)

				names = append(names, name)
				completes = append(completes, complete)
				nextRows = append(nextRows, next_row)
			}
			rows.Close()

			// checking names
			require.Equal(t, []string{"Del", "Prep"}, names, "names are incorrect")

			// checking completes
			require.Equal(t, []bool{true, true}, completes, "should both be true")

			// checking nextRows
			require.Equal(t, []int{1001, 1001}, nextRows, "should both be 1001")

			rows, err = db.Query("SELECT COUNT(*) FROM artists;")
			require.Nil(t, err)

			var count int
			for rows.Next() {
				err = rows.Scan(&count)
				require.Nil(t, err)
			}
			rows.Close()

			// check to see if we deleted all the entries we wanted to remove
			require.Equal(t, baddies, 1000-count, "bulk operations not executed correctly")

			fmt.Println("DONE.\n ")
			fmt.Println("--------------------------------")
		}()
	}
}
