package checker

import (
	"fmt"
	"reflect"
	"runtime"
	"strconv"
	"strings"

	"github.com/pingcap/enterprise-toolkit/tispector/config"
	"github.com/pingcap/enterprise-toolkit/tispector/service/task"
	"github.com/pingcap/enterprise-toolkit/tispector/util/logger"
)

// Rules a slice stores the rules
var Rules []Rule

// RuleMap a map stores the rules
var RuleMap map[string]config.RuleConf
var breakRules []string

// InitRule Init Rules
func InitRule() {

	Rules = []Rule{
		{
			Item:      "OK",
			Name:      "OK",
			Summary:   "OK",
			Content:   "OK",
			Case:      "OK",
			CheckFunc: (*Rule).RuleOK,
		}}

	// RuleMap, err := config.GetRule("")
	// // RuleMap = &rulemap
	RuleFromTable, err := ListAllRules()
	if err != nil {
		logger.Fatal("GetRule failed ; error: %v", err)
	}
	RuleFunc := makeFuncMap()
	for _, rule := range RuleFromTable {
		var rulee Rule
		_, ok := RuleFunc[rule.Checkfunc]
		if ok {
			rulee.CheckFunc = RuleFunc[rule.Checkfunc]
		}
		if !ok {
			logger.Errorf("规则 %s 未定义执行方法", rule.Item)
			rule.Close = 0
		}
		// if rule.Close == 0 {
		// 	continue
		// }
		rulee.Item = rule.Item
		rulee.Name = rule.Name
		rulee.Summary = rule.Summary
		rulee.Content = rule.Content
		if rule.Close == 1 {
			rulee.Close = false
		} else if rule.Close == 0 {
			rulee.Close = true
		} else {
			rulee.Close = true
			logger.Fatal(" The parameter  %s  setting is wrong", rule.Item)
		}
		rulee.Level = rule.Level
		rulee.Case = rule.Case
		rulee.Position = rule.Position

		rulee.Threshold = rule.Threshold
		Rules = append(Rules, rulee)
	}
	// lastRule := Rule{
	// 	Item:      "NotOK",
	// 	Name:      "Statement does not match any existing rules",
	// 	Summary:   "未命中任何已有规则",
	// 	CheckFunc: (*Rule).RuleNull,
	// }
	// Rules = append(Rules, lastRule)
	breakRules = []string{
		RuleMap["RuleDml008"].Item,
		RuleMap["RuleAlter004"].Item,
	}
}

// Rule Define structure Rule
type Rule struct {
	// Rule code
	Item string `json:"Item"`
	// Rule name
	Name string `json:"Name"`
	// Rule Summary
	Summary string `json:"Summary"`
	// Rule content
	Content string `json:"Content"`
	// Whether the rule is disabled
	Close bool `json:"close"`
	// Rule level
	Level int `json:"proiority"`
	// SQL example
	Case string `json:"Case"`
	// 建议所处SQL字符位置，默认0表示全局建议
	Position int `json:"Position"`
	// 函数名
	CheckFunc func(*Rule, *Audit, *task.DBInfo) (pass bool, newSummary string, affectRows int) `json:"-"`
	// 阈值
	Threshold string `json:"Threshold"`
}

//

// makeFuncMap Create a map of functions and functionName
// 读取规则的配置文件，再把它和heuristic中的实现方法结合起来是主要的难点，这里通过构造一个map来实现对应
func makeFuncMap() map[string]func(*Rule, *Audit, *task.DBInfo) (pass bool, newSummary string, affectRows int) {

	funcs := []func(*Rule, *Audit, *task.DBInfo) (pass bool, newSummary string, affectRows int){
		(*Rule).RuleAlterTableDropColumn,
		(*Rule).RuleAlterTableDrop,
		(*Rule).RuleAffectRows,
		(*Rule).RuleBanAddMulti,
		(*Rule).RuleUnsupportedType,
		(*Rule).RuleAlterUseForeignKey,
		(*Rule).RuleAddCharCol,
		(*Rule).RuleSQLUseBlob,
		(*Rule).RuleDdlIdxNoname,
		(*Rule).RuleVarcharSmall,
		(*Rule).RuleDDLUseCharset,
		(*Rule).RuleDdlTabMulJoin,
		(*Rule).RuleAlterViewUpdate,
		(*Rule).RuleAlterViewSubquery,
		(*Rule).RuleAlterViewJoin,
		(*Rule).RuleCreateTableCharset,
		(*Rule).RuleCreateTableComment,
		(*Rule).RuleCreateTableIndex,
		(*Rule).RuleColCommentCheck,
		(*Rule).RuleCreateTableUniqIndex,
		(*Rule).RuleCreateTableNormalIndex,
		(*Rule).RuleCreateTableIndexColNum,
		(*Rule).RuleCreateTableIndexNum,
		(*Rule).RuleCreateTableDupIndex,
		(*Rule).RuleCreateTableNotNullValue,
		(*Rule).RuleCreateTableSetColCharset,
		(*Rule).RuleCreateTableCoIndexOrder,
		(*Rule).RuleCreateTableCoIndexEx,
		(*Rule).RuleCreateTableIndexLen,
		(*Rule).RuleCreateTableTextColNum,
		(*Rule).RuleCreateTableNotUseKeyWorld,
		(*Rule).RuleNotUseIntAsPrimaryKey,
		(*Rule).RuleVarcharLengthLimit,
		(*Rule).RuleAutoIncrementLimit,
		(*Rule).RuleCreateTableWithOutDate,
		(*Rule).RuleCreateTooManyColumn,
		(*Rule).RuleCreateTableNotUseForeignKey,
		(*Rule).RuleCreateTableUsePartitioning,
		(*Rule).RuleCreateSequenceNoCache,
		(*Rule).RuleColUseText,
		(*Rule).RuleColUseUsignInt,
		(*Rule).RuleSQLMultiAutoIncr,
		(*Rule).RuleCreateTableUseEnum,
		(*Rule).RuleCreateTablePK,
		(*Rule).RuleCreateTableFK,
		(*Rule).RuleDMLTableNoWhere,
		(*Rule).RuleMeaninglessWhere,
		(*Rule).RuleMultiDeleteUpdate,
		(*Rule).RuleInsertColDef,
		(*Rule).RuleInsertColValueEqual,
		(*Rule).RuleAlterTableExist,
		(*Rule).RuleAlterTableColumnExist,
		(*Rule).RuleDmlIndexMatch,
		(*Rule).RuleDmlNoMoreThan100,
		(*Rule).RuleDmlLimitOversized,
		(*Rule).RuleDmlHavingClause,
		(*Rule).RuleDmlUseOrIndexNotTakeEffect,
		(*Rule).RuleDmlIndexNotTakeEffect,
		(*Rule).RuleDmlProhibitCrossJoin,
		(*Rule).RuleDmlOrderByFieldsExceedsLimit,
		(*Rule).RuleDmlSubqueriesWithNotInOrNotLike,
		(*Rule).RuleDmlWithUnion,
		(*Rule).RuleDmlSubqueriesInWhereClause,
		(*Rule).RuleDmlSubqueriesInFromClause,
		(*Rule).RuleDmlDeletDataFromMultipleTables,
		(*Rule).RuleDmlUpdateFromMultipleTables,
		(*Rule).RuleDmlSQLColCompute,
		(*Rule).RuleDmlSeleNoWhere,
		(*Rule).RuleDmlUseOrderBy,
		(*Rule).RuleSQLCountCol,
		(*Rule).RuleSQLFomatQuery,
		(*Rule).RulePlnIdxRangeSan,
		(*Rule).RulePlnIdxFullSan,
		(*Rule).RulePlnTableAll,
		(*Rule).RulePlnWhereNoIdxCol,
		(*Rule).RulePlnCartesian,
		(*Rule).RulePushdownNotSupportedPredicates,
		(*Rule).RulePushdownToJoinOperator,
		(*Rule).RulePushdownWithUserVaribales,
	}
	RuleFunc := make(map[string]func(*Rule, *Audit, *task.DBInfo) (pass bool, newSummary string, affectRows int))
	for _, ruleFunc := range funcs {
		// 通过反射函数名，来实现对应关系构造map
		str := fmt.Sprint(runtime.FuncForPC(reflect.ValueOf(ruleFunc).Pointer()).Name())
		str = strings.Replace(str, "github.com/pingcap/enterprise-toolkit/tispector/service/checker.(*Rule).", "", -1)

		RuleFunc[str] = ruleFunc
	}

	return RuleFunc
}

// GetThresholdInt turn the Threshold into type int
func (q *Rule) GetThresholdInt() int {
	threshold, err := strconv.Atoi(q.Threshold)
	if err != nil {
		logger.Fatal("规则变量设置错误：", q.Item)
	}
	return threshold
}

// GetThresholdString turn the Threshold into type string
func (q *Rule) GetThresholdString() string {
	return q.Threshold
}

// IsBreakRule ...
func IsBreakRule(name string) bool {
	for _, v := range breakRules {
		if v == name {
			return true
		}
	}
	return false
}
