package test_common

import (
	"code.justin.tv/d8a/iceman/lib/dbconf"
	"code.justin.tv/d8a/iceman/lib/queries"
	"code.justin.tv/d8a/iceman/lib/tables"
	"database/sql"
	"errors"
	"flag"
	_ "github.com/go-sql-driver/mysql"
	_ "github.com/mattn/go-sqlite3"
	"os"
	"testing"
	"time"
)

func ProcessDBTestFlags() (string, TestDB) {
	var testDB TestDB
	var dbType string

	flag.StringVar(&dbType, "dbtype", "", dbconf.MySqlDriver+" / "+dbconf.PostgresDriver+" / "+dbconf.SqliteDriver)
	flag.Parse()

	if dbType == "" {
		dbType = "sqlite"
	}

	switch dbType {
	case dbconf.MySqlDriver:
		testDB = &MySQLTestDB{}
	case dbconf.PostgresDriver:
		testDB = &PostgresTestDB{}
	case dbconf.SqliteDriver:
		testDB = &SqliteTestDB{}
	}

	return dbType, testDB
}

// Default credentials

var PostgresCreds = "user=postgres"
var MysqlCreds = "root"

type TestDB interface {
	SetupDB(t *testing.T) (*sql.DB, queries.DriverQueries, error)
	CleanupDB() error
	TableExists(tableName string) (bool, error)
}

type PostgresTestDB struct {
	dbName string
	sqlDB  *sql.DB
}

type MySQLTestDB struct {
	dbName string
	sqlDB  *sql.DB
}

type SqliteTestDB struct {
	dbFileName string
	sqlDB      *sql.DB
}

func (tdb *PostgresTestDB) SetupDB(t *testing.T) (*sql.DB, queries.DriverQueries, error) {

	tdb.dbName = "testdb"
	err := tdb.dropTestDB()
	if err != nil {
		return nil, nil, err
	}

	err = tdb.createTestDB()
	if err != nil {
		return nil, nil, err
	}

	tdb.sqlDB, err = sql.Open("postgres", mkPostgresConnStr(tdb.dbName))
	if err != nil {
		return nil, nil, err
	}

	err = tables.VerifyIcemanTables(tdb.sqlDB, &queries.PostgresQueries{})
	if err != nil {
		return nil, nil, err
	}

	return tdb.sqlDB, &queries.PostgresQueries{}, nil
}

func (tdb *PostgresTestDB) CleanupDB() error {
	if tdb.sqlDB != nil {
		// Postgres seemes to linger a connection under some circumstances, so terminate it.
		tdb.sqlDB.Exec("SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '" + tdb.dbName + "'")
		time.Sleep(5 * time.Second)

		tdb.sqlDB.Close()
		tdb.sqlDB = nil
	}

	err := tdb.dropTestDB()
	return err
}

func (tdb *PostgresTestDB) TableExists(tableName string) (bool, error) {

	if tdb.sqlDB == nil {
		return false, errors.New("Database is not open.")
	}

	out := 0
	err := tdb.sqlDB.QueryRow("SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name='" + tableName + "'").Scan(&out)
	if err != nil {
		return false, err
	}

	return out == 1, nil
}

func mkPostgresConnStr(dbName string) string {
	return PostgresCreds + " host=localhost port=5432 sslmode=disable dbname=" + dbName
}

func (tdb *PostgresTestDB) createTestDB() error {
	nodbConn, err := sql.Open("postgres", mkPostgresConnStr("postgres"))
	if err != nil {
		return err
	}
	defer nodbConn.Close()

	_, err = nodbConn.Exec("CREATE DATABASE " + tdb.dbName + ";")
	if err != nil {
		return err
	}

	return nil
}

func (tdb *PostgresTestDB) dropTestDB() error {
	nodbConn, err := sql.Open("postgres", mkPostgresConnStr("postgres"))
	if err != nil {
		return err
	}
	defer nodbConn.Close()

	_, err = nodbConn.Exec("DROP DATABASE IF EXISTS " + tdb.dbName + ";")
	if err != nil {
		return err
	}

	return nil
}

func mkMySqlConnStr(dbName string) string {
	return MysqlCreds + "@/" + dbName + "?parseTime=true"
}

func (tdb *MySQLTestDB) createTestDB() error {
	nodbConn, err := sql.Open("mysql", mkMySqlConnStr(""))
	if err != nil {
		return err
	}
	defer nodbConn.Close()

	_, err = nodbConn.Exec("CREATE DATABASE " + tdb.dbName + ";")
	if err != nil {
		return err
	}

	return nil
}

func (tdb *MySQLTestDB) dropTestDbIfExists() error {
	nodbConn, err := sql.Open("mysql", mkMySqlConnStr(""))
	if err != nil {
		return err
	}
	defer nodbConn.Close()

	_, err = nodbConn.Exec("DROP DATABASE IF EXISTS " + tdb.dbName + ";")
	if err != nil {
		return err
	}

	return nil
}

func (tdb *MySQLTestDB) SetupDB(t *testing.T) (*sql.DB, queries.DriverQueries, error) {
	tdb.dbName = "testdb"

	var err error

	err = tdb.dropTestDbIfExists()
	if err != nil {
		return nil, nil, err
	}

	err = tdb.createTestDB()
	if err != nil {
		return nil, nil, err
	}

	tdb.sqlDB, err = sql.Open("mysql", mkMySqlConnStr(tdb.dbName))
	if err != nil {
		return nil, nil, err
	}

	err = tables.VerifyIcemanTables(tdb.sqlDB, &queries.MySQLQueries{})
	if err != nil {
		return nil, nil, err
	}

	return tdb.sqlDB, &queries.MySQLQueries{}, nil
}

func (tdb *MySQLTestDB) CleanupDB() error {
	if tdb.sqlDB != nil {
		tdb.sqlDB.Close()
		tdb.sqlDB = nil
	}

	err := tdb.dropTestDbIfExists()

	return err
}

func (tdb *MySQLTestDB) TableExists(tableName string) (bool, error) {

	if tdb.sqlDB == nil {
		return false, errors.New("Database is not open.")
	}

	out := 0
	err := tdb.sqlDB.QueryRow("SELECT 1 FROM information_schema.tables WHERE table_schema = '" + tdb.dbName + "' AND table_name='" + tableName + "'").Scan(&out)
	if err != nil {
		return false, err
	}

	return out == 1, nil
}

func (tdb *SqliteTestDB) SetupDB(t *testing.T) (*sql.DB, queries.DriverQueries, error) {
	tdb.dbFileName = "./testdb.db"
	tdb.CleanupDB()

	db, err := sql.Open("sqlite3", tdb.dbFileName)
	if err != nil {
		return nil, nil, err
	}

	tdb.sqlDB = db

	err = tables.VerifyIcemanTables(tdb.sqlDB, &queries.SqliteQueries{})
	if err != nil {
		return nil, nil, err
	}

	return db, &queries.SqliteQueries{}, nil
}

func (tdb *SqliteTestDB) CleanupDB() error {
	if tdb.sqlDB != nil {
		tdb.sqlDB.Close()
	}
	os.Remove(tdb.dbFileName)

	return nil
}

func (tdb *SqliteTestDB) TableExists(tableName string) (bool, error) {

	if tdb.sqlDB == nil {
		return false, errors.New("Database is not open.")
	}

	out := 0
	err := tdb.sqlDB.QueryRow("SELECT 1 FROM sqlite_master WHERE type='table' AND name='" + tableName + "'").Scan(&out)
	if err != nil {
		return false, err
	}

	return out == 1, nil
}
