package migrate

import (
	"fmt"
	"github.com/go-sql-driver/mysql"
	"github.com/jinzhu/gorm"
	"github.com/pingcap/log"
	dump "github.com/xelabs/go-mydumper/src/common"
	"github.com/xelabs/go-mysqlstack/xlog"
	"go.uber.org/zap"
	"gopkg.in/gormigrate.v1"
	"os"
	"path/filepath"
	"strings"
	"time"
)

func UpgradeDB(dsn string, backupDB bool, backupPathPrefix string) (string, error) {
	defaultDBName := "tep"
	//no database is created, let's try to create database first
	cfg, err := mysql.ParseDSN(dsn)
	if err != nil {
		log.Error("can not parse the dsn", zap.String("dsn", dsn), zap.Error(err))
		return dsn, err
	}
	oldDBName := cfg.DBName
	if oldDBName == "" {
		oldDBName = defaultDBName
	}
	cfg.DBName = oldDBName
	dsn = cfg.FormatDSN()

	db, err := gorm.Open("mysql", dsn)
	if err != nil {
		//clean the db configuration and connect to database again
		cfg.DBName = ""
		newDsn := cfg.FormatDSN()
		db, err = gorm.Open("mysql", newDsn)
		if err != nil {
			log.Error("can not connect to database", zap.String("dsn", newDsn), zap.Error(err))
			return dsn, err
		}

		//ok create the database
		db.Exec(fmt.Sprintf("create database if not exists %s", oldDBName))
		log.Info("create database successfully", zap.String("db", oldDBName))
		err = db.Close()
		if err != nil {
			log.Error("close database failed", zap.String("db", oldDBName), zap.Error(err))
			return dsn, err
		}

		cfg.DBName = oldDBName
		//ok try to open database again
		db, err = gorm.Open("mysql", dsn)
		if err != nil {
			log.Error("open database failed", zap.String("db", oldDBName), zap.Error(err))
			return dsn, err
		}

		db.SingularTable(true)
		goMigration := gormigrate.New(db, gormigrate.DefaultOptions, migrationRecords)
		goMigration.InitSchema(initTEPDB)
		err = goMigration.Migrate()
		if err != nil {
			log.Error("init tep database failed", zap.String("db", oldDBName), zap.Error(err))
			return dsn, err
		}
	}
	db.SingularTable(true)
	err = doMigrate(cfg, db, backupDB, backupPathPrefix)
	if err != nil {
		log.Error("upgrade failed, please fix the error", zap.Error(err))
		return dsn, err
	}
	err = db.Close()
	if err != nil {
		log.Error("close database failed", zap.String("db", oldDBName), zap.Error(err))
		return dsn, err
	}
	return dsn, nil
}

func doMigrate(cfg *mysql.Config, db *gorm.DB, backupDB bool, backupPathPrefix string) error {
	goMigration := gormigrate.New(db, gormigrate.DefaultOptions, migrationRecords)
	backUpDir := ""
	//TODO: check if we wil do migration, if not we don't need to backup the database
	if backupDB && strings.TrimSpace(backupPathPrefix) != "" {
		dir, err := backup(cfg, backupPathPrefix)
		if err != nil {
			log.Error("backup database failed, please fix the error and restart the TEP server", zap.Error(err))
			return err
		}
		backUpDir = dir
	}

	err := goMigration.Migrate()
	if err != nil && backupDB && backUpDir != "" {
		log.Info("restore database")
		err = restore(db, cfg, backUpDir)
		if err != nil {
			return err
		}
	}
	return err
}

//TODO: mysqldump is better?
func restore(db *gorm.DB, cfg *mysql.Config, outDir string) error {
	db = db.Exec("drop database if exists " + cfg.DBName)
	if db.Error != nil {
		log.Error("can not drop old database", zap.Error(db.Error))
		return db.Error
	}
	db = db.Exec("create database if not exists " + cfg.DBName)
	args := &dump.Args{
		User:          cfg.User,
		Password:      cfg.Passwd,
		Address:       cfg.Addr,
		Database:      cfg.DBName,
		Table:         "",
		Outdir:        outDir,
		ChunksizeInMB: 128,
		Threads:       1,
		StmtSize:      1000000,
		IntervalMs:    10 * 1000,
		SessionVars:   "",
	}
	// TODO: error handling
	dump.Loader(xlog.NewStdLog(xlog.Level(xlog.INFO)), args)
	return nil
}

func backup(cfg *mysql.Config, path string) (string, error) {
	log.Info("start to backup database....")
	if !strings.HasSuffix(path, "/") {
		path = path + "/"
	}
	dir := fmt.Sprintf("%s%s", path, time.Now().Format("2006-01-02-15-04-05"))
	err := os.MkdirAll(dir, os.ModePerm)
	if err != nil {
		return "", nil
	}
	abs, err := filepath.Abs(dir)
	if err != nil {
		return "", nil
	}
	args := &dump.Args{
		User:          cfg.User,
		Password:      cfg.Passwd,
		Address:       cfg.Addr,
		Database:      cfg.DBName,
		Table:         "",
		Outdir:        abs,
		ChunksizeInMB: 128,
		Threads:       1,
		StmtSize:      1000000,
		IntervalMs:    10 * 1000,
		SessionVars:   "",
	}
	dump.Dumper(xlog.NewStdLog(xlog.Level(xlog.INFO)), args)
	log.Info("backup database done", zap.String("dir", abs))
	return abs, nil
}
