package controller

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"os"
	"regexp"
	"runtime"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/tispector/code"
	"github.com/pingcap/tispector/service/checker"
	"github.com/pingcap/tispector/service/db_info"
	"github.com/pingcap/tispector/service/sql_util"
	"github.com/pingcap/tispector/service/task"
	"github.com/pingcap/tispector/util/logger"
	"github.com/romberli/go-util/common"
)

const (
	fileName     = "Name"
	fileLocation = "Location"
	fileCt       = "Ct"

	SQLFileNameStruct = "Name"
	ClusterNameStruct = "ClusterName"
	DbNameStruct      = "DbName"
	RuleItemsStruct   = "RuleItems"
)

func CheckSQLFile(ctx *gin.Context) Resp {
	var fields map[string]interface{}
	//考虑到规则可能被修改了，重新初始化一下规则
	checker.InitRule()
	data, err := ctx.GetRawData()
	if err != nil {
		logger.Errorf("checkSQLFile error,%s", err)
		return Resp{Message: fmt.Sprintf("update rule failed, %s", err.Error()), Code: code.ParamInvalid}
	}
	fields, err = common.UnmarshalToMapWithStructTag(data, &SQLFile{}, "json")
	if err != nil {
		logger.Errorf("checkSQLFile error,%s", err)
		return Resp{Message: fmt.Sprintf("CheckSQLFile failed,%s", err.Error()), Code: code.ParamInvalid}
	}
	_, sqlFileExists := fields[SQLFileNameStruct]
	_, clusterNameExists := fields[ClusterNameStruct]
	_, dbNameExists := fields[DbNameStruct]
	_, ruleItemsExists := fields[RuleItemsStruct]

	if !sqlFileExists || !clusterNameExists || !dbNameExists || !ruleItemsExists {
		return Resp{Message: fmt.Sprintf("CheckSQLFile failed, %s, %s, %s and %s can't be null", SQLFileNameStruct, ClusterNameStruct, DbNameStruct, RuleItemsStruct)}
	}

	cluster, err := db_info.GetClusterByName(fields[ClusterNameStruct].(string))
	if err != nil {
		logger.Errorf("checkSQLFile error,%s", err)
		return Resp{Message: fmt.Sprintf("Get Cluster failed: %s", fields[ClusterNameStruct]), Code: code.ParamInvalid}
	}
	// init server
	db, err := sql.Open("mysql",
		fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8", cluster.User, cluster.Pwd, cluster.Addr, fields[DbNameStruct]))
	if err != nil {
		logger.Errorf("checkSQLFile error,%s", err)
		return Resp{Message: fmt.Sprintf("open db conn err: %s", err.Error()), Code: code.ParamInvalid}

	}
	dbInfo := task.DBInfo{DB: db, DefaultDB: db, DBName: fmt.Sprint(fields[DbNameStruct])}
	fileInfo, err := checker.GetFileByName(fields[SQLFileNameStruct].(string))
	if fmt.Sprint(err) != "0 errors occurred:\n\t\n\n" {
		return Resp{Message: fmt.Sprintf("CheckSQLFile failed, %s can't be null ,%s", fields[SQLFileNameStruct], err), Code: code.ParamInvalid}
	}
	_, err = os.Stat(fileInfo.Location)
	if err != nil {
		logger.Errorf("checkSQLFile error,%s", err)
		return Resp{Message: fmt.Sprintf("Get file failed: %s", fileInfo.Location), Code: code.ParamInvalid}
	}
	if fields[SQLFileNameStruct] != "" {
		err, sqls := sql_util.SqlReadIn(fileInfo.Location)
		if err != nil {
			logger.Errorf("checkSQLFile error,%s", err)
			return Resp{Message: fmt.Sprintf("CheckSQLFile failed,%s", err.Error()), Code: code.ParamInvalid}
		}
		var resultInFiles []ResultInFile
		for _, sql := range sqls {
			resultInFiles = append(resultInFiles, ResultInFile{Sql: sql, Results: checker.Checker.SqlTotCheckByRuleItems(sql, "", "", &dbInfo, fields[RuleItemsStruct].([]string))})
		}
		bytes, err := json.Marshal(resultInFiles)
		if err != nil {
			logger.Errorf("checkSQLFile error,%s", err)
			return Resp{Message: fmt.Sprintf("CheckSQLFile Failed: %s", err.Error()), Code: code.ParamInvalid}
		}
		jsonStr := string(bytes)

		// fileInfo.Result = jsonStr
		// err = checker.UpdateFile(fileInfo)
		// if err != nil {
		// 	logger.Errorf("checkSQLFile error,%s", err)
		// 	return Resp{Message: fmt.Sprintf("CheckSQLFile Failed: %s", err.Error()), Code: code.ParamInvalid}
		// }
		return Resp{Code: code.Success, Message: fmt.Sprintf("CheckSQLFile Success : %s", fileInfo.Location), Data: jsonStr}

	}
	if fields[SQLFileNameStruct] == "" {
		return Resp{Message: fmt.Sprintf("CheckSQLFile failed, %s can't be null", fields[SQLFileNameStruct]), Code: code.ParamInvalid}
	}
	dbInfo.CloseConn()
	return Resp{}

}

// LisFiles 列出所有文件信息
func LisFiles(ctx *gin.Context) Resp {
	files, err := checker.ListAllFiles()
	if err != nil {
		logger.Errorf("LisFiles error %s", err)
		return Resp{Message: fmt.Sprintf("listFiles failed, %s", err.Error()), Code: code.ParamInvalid}
	}

	var Files []checker.FileInfo
	for _, file := range files {
		File := checker.TransFile(file)
		Files = append(Files, File)
	}
	return Resp{
		Code:    code.Success,
		Message: fmt.Sprintf("ListFiles Success"),
		Data: ListData{
			Items:  Files,
			Total:  len(Files),
			More:   false,
			Offset: 0,
		}}
}

// GetFileByName 通过名字获取文件信息
func GetFileByName(ctx *gin.Context) Resp {
	nameStr := ctx.Query("name")
	file, err := checker.GetFileByName(nameStr)
	if fmt.Sprint(err) != "0 errors occurred:\n\t\n\n" {
		logger.Errorf("GetFileByName failed ,error: %s", err)
		return Resp{Message: fmt.Sprintf("Get fileInfo %s Failed : %s", nameStr, err.Error()), Code: code.ParamInvalid}

	}

	var files []checker.TispectorFile
	files = append(files, file)

	var Files []checker.FileInfo
	for _, file := range files {
		File := checker.TransFile(file)
		Files = append(Files, File)
	}

	return Resp{Code: code.Success, Message: fmt.Sprintf("Get rule %s Success", nameStr), Data: Files}
}

// DelFileByName 通过名字删除该文件信息
func DelFileByName(ctx *gin.Context) Resp {
	nameStr := ctx.Query("name")
	file, err := checker.GetFileByName(nameStr)
	if fmt.Sprint(err) != "0 errors occurred:\n\t\n\n" {
		logger.Errorf("DelFile failed,error: %s", err)
		return Resp{Message: fmt.Sprintf("Get fileInfo %s Failed : %s , maybe not exists", nameStr, err.Error()), Code: code.ParamInvalid}

	}

	err = checker.DelFileByName(nameStr)
	if err != nil {
		logger.Errorf("DelFile failed,error: %s", err)
		return Resp{Message: fmt.Sprintf("Del fileInfo %s Failed : %s", nameStr, err.Error()), Code: code.ParamInvalid}
	}

	var files []checker.TispectorFile
	files = append(files, file)

	var Files []checker.FileInfo
	for _, file := range files {
		File := checker.TransFile(file)
		Files = append(Files, File)
	}

	return Resp{
		Code:    code.Success,
		Message: fmt.Sprintf("DelFileInfo %s Success", nameStr),
		Data:    Files,
	}
}

// Uploadfile 上传文件后端接收
func Uploadfile(ctx *gin.Context) Resp {
	//获取表单数据 参数为name值
	f, err := ctx.FormFile("file")
	//错误处理

	if err != nil {
		logger.Errorf("upload file failed ,error： %s", err)
		// ctx.JSON(http.StatusBadRequest, gin.H{
		// 	"error": err,
		// })
		return Resp{Message: fmt.Sprintf("Uploadfile failed %s", f.Filename), Code: code.ParamInvalid}
	} else {
		//将文件保存至本项目根目录中

		newfileName, err := CheckWhetherSQLFILE(f.Filename)
		if err != nil {
			return Resp{Message: fmt.Sprintf("Uploadfile failed %s:%s", f.Filename, err), Code: code.ParamInvalid}
		}
		pwd, err := GetFilePath()
		if err != nil {
			logger.Errorf("upload file failed ,error： %s", err)
			return Resp{Message: fmt.Sprintf("Uploadfile failed %s", f.Filename), Code: code.ParamInvalid}
		}
		fileInfo := checker.TispectorFile{
			Name:     newfileName,
			Location: pwd + newfileName,
			Ct:       time.Now().Unix(),
		}
		fileName, err := checker.AddFileInfo(fileInfo)
		if err != nil {
			logger.Errorf("upload file failed ,error： %s", err)
			if checkDuplicateKey(err) {
				return Resp{Message: fmt.Sprintf("Uploadfile failed %s : 同名文件已存在", fileName), Code: code.ParamInvalid}
			}
			return Resp{Message: fmt.Sprintf("Uploadfile failed %s : %s", fileName, err), Code: code.ParamInvalid}
		}
		ctx.SaveUploadedFile(f, pwd+fileName)

		//保存成功返回正确的Json数据
		return Resp{
			Code:    code.Success,
			Message: fmt.Sprintf("Uploadfile %s Success", fileName),
			Data:    []string{fileName},
		}
	}
}

// 获取文件路径
func GetFilePath() (string, error) {
	pwd, err := os.Getwd()
	if err == nil {
		switch runtime.GOOS {
		case "darwin":
			return pwd + "/sqlFiles/", nil
		case "windows":
			return pwd + "\\sqlFiles\\", nil
		case "linux":
			return pwd + "/sqlFiles/", nil
		default:
			return "", nil
		}
	}

	return "", err
}

// checkDuplicateKey 检查是否重复
func checkDuplicateKey(err error) bool {
	errStr := fmt.Sprint(err)
	errStr = strings.ToLower(errStr)
	errStr = strings.Replace(errStr, " ", "", -1)
	re := regexp.MustCompile("duplicateentry")
	result := re.FindAllStringSubmatch(errStr, -1)
	if len(result) > 0 {
		return true
	}
	return false
}

//	CheckWhetherSQLFILE 检查sql文件
func CheckWhetherSQLFILE(fileName string) (newName string, err error) {
	n := len(fileName)
	if fileName[(n-4):(n)] != ".sql" {
		return "", fmt.Errorf("请上传sql文件")
	}
	name := fileName[0:(n - 4)]
	newName = name + "-" + fmt.Sprint(time.Now().Unix()) + ".sql"
	return newName, nil
}
