package tests

import (
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"runtime"
	"sync"
	"sync/atomic"
	"testing"
	"time"
	"unsafe"

	"github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"

	"github.com/pingcap/test-infra/services/tcms/tests/configtest"
)

var changeTZandDir sync.Once

func Setup(t *testing.T, config *configtest.Config) {
	changeTZandDir.Do(func() {
		loc, _ := time.LoadLocation("Asia/Shanghai")
		atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&time.Local)), unsafe.Pointer(loc))
		Chdir(TCMSProjectRoot())
	})
	if config.Parallel {
		t.Parallel()
	}
	initDB(config.DSN, config.DBFixtures)
}

// Chdir changes current working directory.
func Chdir(workingDir string) {
	if err := os.Chdir(workingDir); err != nil {
		panic(err)
	}
}

func TCMSProjectRoot() string {
	_, filename, _, _ := runtime.Caller(0)
	return path.Join(path.Dir(filename), "..")
}

func DropDB(dsn string) error {
	conn := sqlx.MustConnect("mysql", dsn)
	defer conn.Close()

	dsnCfg, err := mysql.ParseDSN(dsn)
	if err != nil {
		return err
	}
	if _, err = conn.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS `%s`", dsnCfg.DBName)); err != nil {
		return err
	}
	return nil
}

func initDB(dsn string, fixturePaths []string) {
	cfg, err := mysql.ParseDSN(dsn)
	if err != nil {
		panic(fmt.Sprintf("connect test db failed, err=%v", err))
	}
	testDB := cfg.DBName

	// connect without db name to create db
	cfg.DBName = ""
	d := sqlx.MustConnect("mysql", cfg.FormatDSN())
	d.MustExec("drop database if exists " + testDB)
	d.MustExec("create database if not exists " + testDB)
	d.Close()

	// reconnect with db name
	cfg.DBName = testDB
	d = sqlx.MustConnect("mysql", cfg.FormatDSN())
	defer d.Close()

	dbLoad(d, "db/schema.sql")
	for _, fixture := range fixturePaths {
		dbLoad(d, fixture)
	}
}

func dbLoad(d *sqlx.DB, path string) {
	sqls, err := ioutil.ReadFile(path)
	if err != nil {
		panic(fmt.Sprintf("read sql failed, err=%v", err))
	}
	d.MustExec(string(sqls))
}
