package controller

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"os"
	"time"

	"github.com/pingcap/tispector/service/sql_util"

	"github.com/gin-gonic/gin"
	"github.com/romberli/go-util/common"

	"github.com/pingcap/tispector/code"
	"github.com/pingcap/tispector/config"
	"github.com/pingcap/tispector/service/checker"
	"github.com/pingcap/tispector/service/db_info"
	"github.com/pingcap/tispector/service/task"
	"github.com/pingcap/tispector/util/logger"
)

const (
	nameJSON = "name"

	ruleItemStruct      = "Item"
	ruleCloseStruct     = "Close"
	ruleCheckFuncStruct = "CheckFunc"
	ruleNameStruct      = "Name"
	ruleSummaryStruct   = "Summary"
	ruleContentStruct   = "Content"
	ruleLevelStruct     = "Level"
	ruleCaseStruct      = "Case"
	rulePositionStruct  = "Position"
	ruleThresholdStruct = "Threshold"

	SQLInfoContentStruct     = "Content"
	SQLInfoClusterNameStruct = "ClusterName"
	SQLInfoDbName            = "DbName"
	SQLInfoRuleItems         = "RuleItems"
)

type ResultInFile struct {
	Sql     string `json:"sql"`
	Results []checker.Result
}

// LisRule 读取数据库中的规则，列出
func LisRule(ctx *gin.Context) Resp {

	rules, err := checker.ListAllRules()

	if err != nil {
		return Resp{Message: fmt.Sprintf("listRules failed, %s", err.Error()), Code: code.ParamInvalid}
	}

	return Resp{
		Code:    code.Success,
		Message: fmt.Sprintf("ListRules Success"),
		Data: ListData{
			Items:  rules,
			Total:  len(rules),
			More:   false,
			Offset: 0,
		}}
}

// UpdateRuleStatus 暂时用不到，而且实现方法有问题，不能修改int类型的0
func UpdateRuleStatus(ctx *gin.Context) Resp {
	f := "UpdateRuleStatus()-->"

	params := struct {
		Name   string `json:"name" binding:"required"`
		Action string `json:"action" binding:"required"`
	}{}
	if err := ctx.BindJSON(&params); err != nil {
		return Resp{Message: fmt.Sprintf("%s, parse param failed :%s ", f, err.Error()), Code: code.ParamInvalid}
	}

	if err := checker.UpdateRuleStatus(params.Name, params.Action); err != nil {
		return Resp{Message: fmt.Sprintf("%s,update RuleStatus failed :%s ", f, err.Error()), Code: code.ParamInvalid}
	}

	return Resp{
		Code:    code.Success,
		Message: fmt.Sprintf("UpdateRuleStatus Success"),
	}
}

// UpdateRule 对某条规则进行修改，在数据库中进行修改
func UpdateRule(ctx *gin.Context) Resp {
	time.Sleep(1 * time.Microsecond)
	var fields map[string]interface{}
	var rule config.RuleConf
	// 当数据类型为int时，设置99表示为空值
	rule.Close = 99
	rule.Position = 99
	rule.Level = 99
	data, err := ctx.GetRawData()
	if err != nil {
		return Resp{Message: fmt.Sprintf("update rule failed, %s", err.Error()), Code: code.ParamInvalid}
	}
	// unmarshal data
	fields, err = common.UnmarshalToMapWithStructTag(data, &config.RuleConf{}, "json")
	if err != nil {
		return Resp{Message: fmt.Sprintf("update rule failed, %s", err.Error()), Code: code.ParamInvalid}
	}
	ruleItem, ruleItemExists := fields[ruleItemStruct]
	if !ruleItemExists {
		return Resp{Message: fmt.Sprintf("update rule failed,%s  be null", ruleItemStruct), Code: code.ParamInvalid}
	}
	rule.Item = ruleItem.(string)
	ruleClose, ruleCloseExists := fields[ruleCloseStruct]
	if ruleCloseExists {
		if ruleClose.(int) == 0 || ruleClose.(int) == 1 {
			rule.Close = ruleClose.(int)
		} else {
			return Resp{Message: fmt.Sprintf("update rule failed, rule.Close is out of range"), Code: code.ParamInvalid}
		}
	}
	ruleCheckFunc, ruleCheckFuncExists := fields[ruleCheckFuncStruct]
	if ruleCheckFuncExists {
		rule.CheckFunc = ruleCheckFunc.(string)
	}
	ruleName, ruleNameExists := fields[ruleNameStruct]
	if ruleNameExists {
		rule.Name = ruleName.(string)
	}
	ruleSummary, ruleSummaryExists := fields[ruleSummaryStruct]
	if ruleSummaryExists {
		rule.Summary = ruleSummary.(string)
	}
	ruleContent, ruleContentExists := fields[ruleContentStruct]
	if ruleContentExists {
		rule.Content = ruleContent.(string)
	}
	ruleLevel, ruleLevelExists := fields[ruleLevelStruct]
	if ruleLevelExists {
		if ruleLevel.(int) == 1 || ruleLevel.(int) == 2 || ruleLevel.(int) == 3 {
			rule.Level = ruleLevel.(int)
		} else {
			return Resp{Message: fmt.Sprintf("update rule failed, rule.Level is out of range"), Code: code.ParamInvalid}
		}
	}
	ruleCase, ruleCaseExists := fields[ruleCaseStruct]
	if ruleCaseExists {
		rule.Case = ruleCase.(string)
	}
	rulePositon, rulePositionExists := fields[rulePositionStruct]
	if rulePositionExists {
		rule.Position = rulePositon.(int)
	}
	ruleThreshold, ruleThresholdExists := fields[ruleThresholdStruct]
	if ruleThresholdExists {
		rule.Threshold = ruleThreshold.(string)
	}
	err = checker.UpdateRule(rule)
	if fmt.Sprint(err) != "0 errors occurred:\n\t\n\n" {
		fmt.Println(err)
		logger.Error(err)
		return Resp{Message: fmt.Sprintf("update rule Failed : %s", err.Error()), Code: code.ParamInvalid}

	}
	return Resp{
		Code:    code.Success,
		Message: fmt.Sprintf("Update Rule %s Success", fields[ruleItemStruct])}
}

// CheckSQL 进行单条SQL语句的检查
func CheckSQL(ctx *gin.Context) Resp {
	var fields map[string]interface{}
	//考虑到规则可能被修改了，重新初始化一下规则
	checker.InitRule()
	data, err := ctx.GetRawData()
	if err != nil {
		return Resp{Message: fmt.Sprintf("update rule failed, %s", err.Error()), Code: code.ParamInvalid}
	}
	fields, err = common.UnmarshalToMapWithStructTag(data, &SQLInfo{}, "json")
	if err != nil {
		return Resp{Message: fmt.Sprintf("CheckSQL failed,%s", err.Error()), Code: code.ParamInvalid}
	}
	_, sqlInfoContentExists := fields[SQLInfoContentStruct]
	_, sqlInfoClusterNameExists := fields[SQLInfoClusterNameStruct]
	_, sqlInfoDbNameExists := fields[SQLInfoDbName]
	if !sqlInfoContentExists || !sqlInfoClusterNameExists || !sqlInfoDbNameExists {
		return Resp{Message: fmt.Sprintf("CheckSQL failed, %s, %s and %s can't be null", SQLInfoContentStruct, SQLInfoClusterNameStruct, SQLInfoDbName), Code: code.ParamInvalid}
	}
	cluster, err := db_info.GetClusterByName(fields[SQLInfoClusterNameStruct].(string))
	if err != nil {
		return Resp{Message: fmt.Sprintf("Get Cluster failed: %s", fields[SQLInfoClusterNameStruct]), 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[SQLInfoDbName]))
	if err != nil {
		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[SQLInfoDbName])}
	if fields[SQLInfoContentStruct] != "" {
		results := checker.Checker.SqlTotCheck(fmt.Sprint(fields[SQLInfoContentStruct]), "", "", &dbInfo)
		bytes, err := json.Marshal(results)
		if err != nil {
			return Resp{Message: fmt.Sprintf("CheckSQL Failed: %s", err.Error()), Code: code.ParamInvalid}

		}
		jsonStr := string(bytes)
		return Resp{Code: code.Success, Message: fmt.Sprintf("CheckSQL Success, sql: %s", fields[SQLInfoContentStruct]), Data: jsonStr}
	}
	if fields[SQLInfoContentStruct] == "" {
		return Resp{Message: fmt.Sprintf("CheckSQL failed, %s can't be null", SQLInfoContentStruct), Code: code.ParamInvalid}
	}
	dbInfo.CloseConn()
	return Resp{}
}

// GetRuleByItem 会从数据库中以Item为检索条件，返回规则
func GetRuleByItem(ctx *gin.Context) Resp {

	itemStr := ctx.Query("item")

	rule, err := checker.GetRuleByItem(itemStr)
	if fmt.Sprint(err) != "0 errors occurred:\n\t\n\n" {
		return Resp{Message: fmt.Sprintf("Get rule %s Failed : %s", itemStr, err.Error()), Code: code.ParamInvalid}

	}
	var rules []checker.TispectorRule
	rules = append(rules, rule)
	return Resp{Code: code.Success, Message: fmt.Sprintf("Get rule %s Success", itemStr), Data: rules}

}

// CheckSQLPRO 进行单条SQL语句的检查，这条是因为前端要求能够返回指定规则进行check，而不是以全局规则的开关来check
func CheckSQLPRO(ctx *gin.Context) Resp {
	var fields map[string]interface{}
	//考虑到规则可能被修改了，重新初始化一下规则
	checker.InitRule()
	data, err := ctx.GetRawData()
	if err != nil {
		return Resp{Message: fmt.Sprintf("update rule failed, %s", err.Error()), Code: code.ParamInvalid}
	}
	fields, err = common.UnmarshalToMapWithStructTag(data, &SQLInfo{}, "json")
	if err != nil {
		return Resp{Message: fmt.Sprintf("CheckSQL failed,%s", err.Error()), Code: code.ParamInvalid}
	}
	_, sqlInfoContentExists := fields[SQLInfoContentStruct]
	_, sqlInfoClusterNameExists := fields[SQLInfoClusterNameStruct]
	_, sqlInfoDbNameExists := fields[SQLInfoDbName]
	_, sqlInfoRuleItemsExists := fields[SQLInfoRuleItems]
	if !sqlInfoContentExists || !sqlInfoClusterNameExists || !sqlInfoDbNameExists || !sqlInfoRuleItemsExists {
		return Resp{Message: fmt.Sprintf("CheckSQL failed, %s, %s, %s and %s can't be null", SQLInfoContentStruct, SQLInfoClusterNameStruct, SQLInfoDbName, SQLInfoRuleItems), Code: code.ParamInvalid}
	}
	cluster, err := db_info.GetClusterByName(fields[SQLInfoClusterNameStruct].(string))
	if err != nil {
		return Resp{Message: fmt.Sprintf("Get Cluster failed: %s", fields[SQLInfoClusterNameStruct]), 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[SQLInfoDbName]))
	if err != nil {
		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[SQLInfoDbName])}
	if fields[SQLInfoContentStruct] != "" {
		results := checker.Checker.SqlTotCheckByRuleItems(fmt.Sprint(fields[SQLInfoContentStruct]), "", "", &dbInfo, fields[SQLInfoRuleItems].([]string))
		bytes, err := json.Marshal(results)
		if err != nil {
			return Resp{Message: fmt.Sprintf("CheckSQL Failed: %s", err.Error()), Code: code.ParamInvalid}

		}
		jsonStr := string(bytes)
		return Resp{Code: code.Success, Message: fmt.Sprintf("CheckSQL Success, sql: %s", fields[SQLInfoContentStruct]), Data: jsonStr}
	}
	if fields[SQLInfoContentStruct] == "" {
		return Resp{Message: fmt.Sprintf("CheckSQL failed, %s can't be null", SQLInfoContentStruct), Code: code.ParamInvalid}
	}
	dbInfo.CloseConn()

	return Resp{}
}

// CheckSQLInFile 进行多条SQL语句的检查
func CheckSQLInFile(ctx *gin.Context) Resp {
	var fields map[string]interface{}
	//考虑到规则可能被修改了，重新初始化一下规则
	checker.InitRule()
	data, err := ctx.GetRawData()
	if err != nil {
		return Resp{Message: fmt.Sprintf("update rule failed, %s", err.Error()), Code: code.ParamInvalid}
	}
	fields, err = common.UnmarshalToMapWithStructTag(data, &SQLInfo{}, "json")
	if err != nil {
		return Resp{Message: fmt.Sprintf("CheckSQL failed,%s", err.Error()), Code: code.ParamInvalid}
	}
	_, sqlInfoContentExists := fields[SQLInfoContentStruct]
	_, sqlInfoClusterNameExists := fields[SQLInfoClusterNameStruct]
	_, sqlInfoDbNameExists := fields[SQLInfoDbName]
	if !sqlInfoContentExists || !sqlInfoClusterNameExists || !sqlInfoDbNameExists {
		return Resp{Message: fmt.Sprintf("CheckSQL failed, %s, %s and %s can't be null", SQLInfoContentStruct, SQLInfoClusterNameStruct, SQLInfoDbName), Code: code.ParamInvalid}
	}
	cluster, err := db_info.GetClusterByName(fields[SQLInfoClusterNameStruct].(string))
	if err != nil {
		return Resp{Message: fmt.Sprintf("Get Cluster failed: %s", fields[SQLInfoClusterNameStruct]), 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[SQLInfoDbName]))
	if err != nil {
		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[SQLInfoDbName])}
	_, err = os.Stat(fmt.Sprint(fields[SQLInfoContentStruct]))
	if err != nil {
		return Resp{Message: fmt.Sprintf("Get file failed: %s", fields[SQLInfoContentStruct]), Code: code.ParamInvalid}
	}
	if fields[SQLInfoContentStruct] != "" {
		err, sqls := sql_util.SqlReadIn(fmt.Sprint(fields[SQLInfoContentStruct]))
		if err != nil {
			return Resp{Message: fmt.Sprintf("CheckSQL failed,%s", err.Error()), Code: code.ParamInvalid}
		}
		var resultInFiles []ResultInFile
		for _, sql := range sqls {
			resultInFiles = append(resultInFiles, ResultInFile{Sql: sql, Results: checker.Checker.SqlTotCheck(sql, "", "", &dbInfo)})
		}
		bytes, err := json.Marshal(resultInFiles)
		if err != nil {
			return Resp{Message: fmt.Sprintf("CheckSQL Failed: %s", err.Error()), Code: code.ParamInvalid}
		}
		jsonStr := string(bytes)

		return Resp{Code: code.Success, Message: fmt.Sprintf("CheckSQLFile Success : %s", fields[SQLInfoContentStruct]), Data: jsonStr}

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

// InitRuleTable 初始化规则表，往表中填入规则
func InitRuleTable(ctx *gin.Context) Resp {
	err := checker.InitRuleTable()
	if fmt.Sprint(err) != "0 errors occurred:\n\t\n\n" {
		fmt.Println(err)
		logger.Error(err)
		return Resp{Message: fmt.Sprintf("InitRuleTable Failed : %s", err.Error()), Code: code.ParamInvalid}

	}
	return Resp{Code: code.Success, Message: fmt.Sprintf("InitRuleTable Success")}
}

// CheckSQLPRO 进行单条SQL语句的检查，可指定规则
func CheckSQLByRules(content string, items []string, cluster TestCluster, dbName string) ([]checker.Result, error) {

	//考虑到规则可能被修改了，重新初始化一下规则
	checker.InitRule()

	// cluster, err := db_info.GetClusterByName("clusterName")
	// if err != nil {
	// 	return nil, err
	// }
	// init server
	db, err := sql.Open("mysql",
		fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8", cluster.User, cluster.Pwd, cluster.Addr, dbName))
	if err != nil {
		return nil, err

	}
	dbInfo := task.DBInfo{DB: db, DefaultDB: db, DBName: fmt.Sprint(dbName)}
	if content != "" {
		results := checker.Checker.SqlTotCheckByRuleItems(fmt.Sprint(content), "", "", &dbInfo, items)

		return results, nil
	}
	if content == "" {
		return nil, err
	}
	dbInfo.CloseConn()

	return nil, nil
}

type TestCluster struct {
	User string
	Pwd  string
	Addr string
}
