package tests

import (
	"encoding/json"
	"fmt"
	"math/rand"
	"os"
	"path"
	"testing"

	"runtime"

	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/db"
	"github.com/pingcap/dbaas/billing2/migrate"
	"github.com/pingcap/dbaas/billing2/utils/panicIf"
	"github.com/shopspring/decimal"
	"github.com/spf13/viper"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
	"gorm.io/gorm"
)

var gormdb *gorm.DB = nil

func setupTestConfigFromEnv() {
	result := map[string](interface{}){}
	mysqlTestConfig, ok := os.LookupEnv("MYSQL_TEST_CONFIG")
	if !ok {
		return
	}
	panicIf.Error(json.Unmarshal([]byte(mysqlTestConfig), &result))
	viper.Set(config.MYSQLPort, result["port"])
	viper.Set(config.MySQLHost, result["host"])
	viper.Set(config.MySQLUser, result["user"])
	viper.Set(config.MySQLPassword, result["password"])
}

func setupDB() {
	viper.Set(config.Database, fmt.Sprintf("unittest_dbaas_%v", rand.Int()))
	gormdb, err := db.GetSetupDB()
	panicIf.Error(err)
	panicIf.Error(gormdb.Exec(fmt.Sprintf("drop database if exists %v", viper.GetString(config.Database))).Error)
	panicIf.Error(gormdb.Exec(fmt.Sprintf("create database %v  CHARACTER SET utf8mb4 COLLATE utf8mb4_bin", viper.GetString(config.Database))).Error)
	_, err = db.Initialize()
	panicIf.Error(err)
	panicIf.Error(migrate.MigrateDB(db.GetDB(), false))
}

func tearDownDB() {
	if gormdb != nil {
		panicIf.Error(gormdb.Exec(fmt.Sprintf("drop database if exists %v", viper.GetString(config.Database))).Error)
	}
}

type BillingTestSuite struct {
	suite.Suite
}

func (*BillingTestSuite) SetupTest() {
	Setup()
}

func (*BillingTestSuite) TearDownTest() {
	Shutdown()
}

func SetupConfig() {
	_, codeFile, _, _ := runtime.Caller(0)
	viper.AddConfigPath(path.Join(path.Dir(codeFile), "../../config/"))
	panicIf.Error(config.Init())
	setupTestConfigFromEnv()
}

func InitCache() {
	viper.Set(config.EnableCache, false)
}

func Setup() {
	SetupConfig()
	setupDB()
	InitCache()
}

func Shutdown() {
	tearDownDB()
}

type BillingAssert struct {
	*require.Assertions
	T *testing.T
}

func (assert *BillingAssert) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
	assert.T.Helper()
	expectedLeft, leftOk := expected.(decimal.Decimal)
	expectedRight, rightOk := actual.(decimal.Decimal)
	if leftOk && rightOk {
		if !expectedLeft.Equal(expectedRight) {
			assert.Fail(fmt.Sprintf("Not equal: \n"+
				"expected: %s\n"+
				"actual  : %s", expectedLeft.String(), expectedRight.String()), msgAndArgs...)
		}
		return
	}
	assert.Assertions.Equal(expected, actual, msgAndArgs...)
}

func (suite *BillingTestSuite) Assert() *BillingAssert {
	t := suite.T()
	rawAssert := require.New(t)
	return &BillingAssert{rawAssert, t}
}
