package checker

import (
	"fmt"

	"regexp"
	"strconv"
	"strings"

	"github.com/pingcap/parser/ast"
	"github.com/pingcap/parser/mysql"
	"github.com/pingcap/tispector/service/sql_util"
	"github.com/pingcap/tispector/service/task"
	"github.com/pingcap/tispector/util/logger"
	"vitess.io/vitess/go/vt/sqlparser"
)

// IndexMatch DML010
func (q *Rule) RuleDmlLimitOversized(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RuleDmlLimitOversized")
	pass = true
	thresholdOffset := q.GetThresholdInt()

	sqlparser.Walk(func(node sqlparser.SQLNode) (continueWalk bool, err error) {
		switch n := node.(type) {
		case *sqlparser.Select:
			if n.Limit != nil {
				bs := n.Limit.Rowcount.(*sqlparser.SQLVal).Val
				ba := []byte{}
				for _, b := range bs {
					ba = append(ba, byte(b))
				}

				tmp := string(ba)
				limit, err := strconv.Atoi(tmp)
				if err != nil {
					pass = true
					return false, err
				}
				if limit >= thresholdOffset {
					pass = false
					return true, nil
				}
			}
		}
		return true, nil
	}, audit.Stmt)
	return pass, q.Summary, 0
}

// IndexMatch DML011
func (q *Rule) RuleDmlHavingClause(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RuleDmlHavingClause")
	pass = true
	for _, tiStmt := range audit.TiStmt {
		switch node := tiStmt.(type) {
		case *ast.SelectStmt:
			if node.Having != nil {
				pass = false
				return pass, q.Summary, 0
			}
			for _, stmt := range audit.SelectStmts {
				if stmt.(*ast.SelectStmt).Having != nil {
					pass = false
					return pass, q.Summary, 0
				}
			}
		}
	}
	return pass, q.Summary, 0
}

// IndexMatch DML012
func (q *Rule) RuleDmlUseOrIndexNotTakeEffect(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RuleDmlUseOrIndexNotTakeEffect")
	pass = true
	for _, tiStmt := range audit.TiStmt {
		switch tiStmt.(type) {
		case *ast.SelectStmt:

		}
	}

	return pass, q.Summary, 0
}

// IndexMatch DML013
func (q *Rule) RuleDmlIndexNotTakeEffect(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RuleDmlIndexNotTakeEffect")
	pass = true
	for _, tiStmt := range audit.TiStmt {
		switch tiStmt.(type) {
		case *ast.SelectStmt:
		}
	}

	return pass, q.Summary, 0
}

// IndexMatch DML014
func (q *Rule) RuleDmlProhibitCrossJoin(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RuleDmlProhibitCrossJoin")
	pass = true
	for _, tiStmt := range audit.TiStmt {
		switch node := tiStmt.(type) {
		case *ast.SelectStmt:
			if node.From != nil && node.From.TableRefs.Tp == ast.CrossJoin {
				return false, q.Summary, 0
			}
		default:
			res := reGet(audit.Query, "crossjoin")
			if len(res) > 0 {
				return false, q.Summary, 0
			}
		}
	}

	return pass, q.Summary, 0
}

// IndexMatch DML015
func (q *Rule) RuleDmlOrderByFieldsExceedsLimit(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RuleDmlOrderByFieldsExceedsLimit")

	pass = true
	threshold := q.GetThresholdInt()
	for _, tiStmt := range audit.TiStmt {
		switch node := tiStmt.(type) {
		case *ast.SelectStmt:
			if orderBy := node.OrderBy; orderBy != nil {
				if len(orderBy.Items) >= threshold {
					pass = false
					return pass, q.Summary, 0
				}
			}

			for _, stmt := range audit.SelectStmts {
				if orderBy := stmt.(*ast.SelectStmt).OrderBy; orderBy != nil {
					if len(orderBy.Items) >= threshold {
						pass = false
						return pass, q.Summary, 0
					}
				}
			}
		}
	}

	return pass, q.Summary, 0
}

// IndexMatch DML016
func (q *Rule) RuleDmlSubqueriesWithNotInOrNotLike(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RuleDmlSubqueriesWithNotInOrNotLike")

	pass = true
	for _, tiStmt := range audit.TiStmt {
		switch node := tiStmt.(type) {
		case *ast.SelectStmt:
			if node.Where != nil {
				pass = sql_util.NotInOrNotLikeInClause(node.Where)
				if !pass {
					return pass, q.Summary, 0
				}
			}

			for _, stmt := range audit.SelectStmts {
				if whereClause := stmt.(*ast.SelectStmt).Where; whereClause != nil {
					pass = sql_util.NotInOrNotLikeInClause(whereClause)
					if !pass {
						return pass, q.Summary, 0
					}
				}
			}
		}
	}

	return pass, q.Summary, 0
}

// IndexMatch DML017
func (q *Rule) RuleDmlWithUnion(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RuleDmlWithUnion")

	pass = true
	for _, tiStmt := range audit.TiStmt {
		switch tiStmt.(type) {
		case *ast.SetOprStmt:
			return false, q.Summary, 0
		}
	}

	return pass, q.Summary, 0
}

// IndexMatch DML018
func (q *Rule) RuleDmlSubqueriesInWhereClause(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RuleDmlSubqueriesInWhereClause")

	pass = true
	for _, tiStmt := range audit.TiStmt {
		switch node := tiStmt.(type) {
		case *ast.SelectStmt:
			if node.Where != nil {
				pass = sql_util.SubQueryInWhereClause(node.Where)
				if !pass {
					return pass, q.Summary, 0
				}
			}

			for _, stmt := range audit.SelectStmts {
				if whereClause := stmt.(*ast.SelectStmt).Where; whereClause != nil {
					pass = sql_util.SubQueryInWhereClause(whereClause)
					if !pass {
						return pass, q.Summary, 0
					}
				}
			}
		}
	}

	return pass, q.Summary, 0
}

// IndexMatch DML019
func (q *Rule) RuleDmlSubqueriesInFromClause(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RuleDmlSubqueriesInFromClause")

	pass = true
	for _, tiStmt := range audit.TiStmt {
		switch node := tiStmt.(type) {
		case *ast.SelectStmt:
			pass = q.checkSubqueryInFromClause(node, pass)
			if pass {
				for _, stmt := range audit.SelectStmts {
					pass = q.checkSubqueryInFromClause(stmt.(*ast.SelectStmt), pass)
					if !pass {
						return pass, q.Summary, 0
					}
				}
			}
		}
	}
	return pass, q.Summary, 0
}

func (q *Rule) checkSubqueryInFromClause(node *ast.SelectStmt, pass bool) bool {
	nodeFrom := node.From
	if nodeFrom == nil {
		pass = true
		return pass
	}
	var join interface{}
	if join = node.From.TableRefs; join != nil {
		if tableRefs, ok := join.(*ast.Join); ok {
			pass = sql_util.SubQueryInFromClause(tableRefs.Right)
		}
	}
	return pass
}

// IndexMatch DML020
func (q *Rule) RuleDmlDeletDataFromMultipleTables(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RuleDmlDeletDataFromMultipleTables")

	pass = true
	limit := q.GetThresholdInt()
	for _, tiStmt := range audit.TiStmt {
		switch node := tiStmt.(type) {
		case *ast.DeleteStmt:
			if tables := node.Tables; tables != nil {
				if len(tables.Tables) >= limit {
					return false, q.Summary, 0
				}
			}
		}
	}
	return pass, q.Summary, 0
}

// IndexMatch DML021
func (q *Rule) RuleDmlUpdateFromMultipleTables(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RuleDmlUpdateFromMultipleTables")

	pass = true
	limit := q.GetThresholdInt()
	for _, tiStmt := range audit.TiStmt {
		switch node := tiStmt.(type) {
		case *ast.UpdateStmt:
			i := 0
			if tableRefs := node.TableRefs; tableRefs != nil {
				sql_util.UpdateMaxTables(tableRefs.TableRefs, &i)
				if i >= limit {
					pass = false
				}
			}
		}
	}
	return pass, q.Summary, 0
}

// IndexMatch RuleCreate020
func (q *Rule) RuleCreateTableWithOutDate(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RuleCreateTableWithOutDate")

	pass = true
	for _, tiStmt := range audit.TiStmt {
		switch node := tiStmt.(type) {
		case *ast.CreateTableStmt:
			for k := range node.Cols {
				datetype := node.Cols[k].Tp.Tp
				if datetype == mysql.TypeTimestamp || datetype == mysql.TypeDate || datetype == mysql.TypeDatetime {
					fmt.Printf("V =  %v  \n", datetype)
					return true, q.Summary, 0
				}
			}
			return false, q.Summary, 0
		}
	}
	return pass, q.Summary, 0
}

// IndexMatch RuleCreate021
func (q *Rule) RuleCreateTooManyColumn(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RuleCreateTooManyColumn")

	pass = true
	offset := q.GetThresholdInt()
	for _, tiStmt := range audit.TiStmt {
		switch node := tiStmt.(type) {
		case *ast.CreateTableStmt:
			if colNums := len(node.Cols); colNums < offset {
				return true, q.Summary, 0

			}
			return false, q.Summary, 0
		}
	}
	return pass, q.Summary, 0
}

// IndexMatch RuleCreate022
func (q *Rule) RuleCreateTableNotUseForeignKey(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RuleCreateTableNotUseForeignKey")

	pass = true
	for _, tiStmt := range audit.TiStmt {
		switch node := tiStmt.(type) {
		case *ast.CreateTableStmt:
			for _, constraints := range node.Constraints {
				if constraints.Tp == ast.ConstraintForeignKey {
					pass = false
				}
			}
		}
	}
	return pass, q.Summary, 0
}

// IndexMatch RuleCreate023
func (q *Rule) RuleCreateTableUsePartitioning(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RuleCreateTableUsePartitioning")

	pass = true
	for _, tiStmt := range audit.TiStmt {
		switch node := tiStmt.(type) {
		case *ast.CreateTableStmt:
			if node.Partition != nil {
				pass = false
			}
		}
	}
	return pass, q.Summary, 0
}

// IndexMatch RuleCreate024
func (q *Rule) RuleCreateSequenceNoCache(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RuleCreateSequenceNoCache")

	pass = true
	for _, tiStmt := range audit.TiStmt {
		switch node := tiStmt.(type) {
		case *ast.CreateSequenceStmt:
			if seqOptions := node.SeqOptions; seqOptions != nil {
				for _, option := range seqOptions {
					if option.Tp == 8 && option.IntValue > 0 {
						pass = true
						break
					} else {
						pass = false
					}
				}
			}
		}
	}
	return pass, q.Summary, 0
}

// IndexMatch RuleAlter006
func (q *Rule) RuleAlterUseForeignKey(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RuleAlterUseForeignKey")

	for _, tiStmt := range audit.TiStmt {
		switch node := tiStmt.(type) {
		case *ast.AlterTableStmt:
			if len(node.Specs) < 1 {
				return true, q.Summary, 0
			}
			if node.Specs[0].Constraint != nil && node.Specs[0].Constraint.Tp == ast.ConstraintForeignKey {
				return false, q.Summary, 0
			}
		}
	}
	return true, q.Summary, 0
}

// RulePlnIdxRangeSan pln.001
func (q *Rule) RulePlnIdxRangeSan(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RulePlnIdxRangeSan")

	res, err := explainSQL(audit, info)
	if err != nil {
		res := reGet(fmt.Sprintln(err), ("table'([^']+)'doesn'texist"))
		if len(res) < 1 {
			logger.Errorf("%v", err)
		}
		return true, q.Summary, 0
	}
	for _, explain := range res {
		res := reGet(explain.ID, "indexrangescan")
		if res != nil {
			ragInfos := reGet(explain.OperatorInfo, "(range.)\\[(.*?)\\]")
			if ragInfos != nil {
				ragInfo := reGet(ragInfos[0][2], "\\\"(.*?)\\\"")
				if ragInfo[0][1] == ragInfo[1][1] {
					return true, q.Summary, 0
				} else {
					return false, q.Summary, 0
				}
			}
		}
	}
	// fmt.Println(res)
	return true, q.Summary, 0
}

// RulePlnIdxFullSan pln.002
func (q *Rule) RulePlnIdxFullSan(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RulePlnIdxFullSan")

	res, err := explainSQL(audit, info)
	if err != nil {
		res := reGet(fmt.Sprintln(err), ("table'([^']+)'doesn'texist"))
		if len(res) < 1 {
			logger.Errorf("%v", err)
		}
		return true, q.Summary, 0
	}
	for _, explain := range res {
		//res := reGet(explain.ID, "indexfullscan")
		id := explain.ID
		id = strings.ToLower(id)
		id = strings.Replace(id, " ", "", -1)
		re := regexp.MustCompile("indexfullscan")
		if re == nil {
			logger.Errorf("error regexp %s", "indexfullscan")
		}
		res := re.FindAllString(id, -1)

		if res != nil {
			return true, q.Summary, 0
		} else {
			return false, q.Summary, 0
		}
	}
	// fmt.Println(res)
	return true, q.Summary, 0
}

//RulePushdownNotSupportedPredicates PUSHDOWN.001
func (q *Rule) RulePushdownNotSupportedPredicates(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RulePushdownNotSupportedPredicates")
	pass = true

	res, err := explainSQL(audit, info)
	if err != nil {
		res := reGet(fmt.Sprintln(err), ("table'([^']+)'doesn'texist"))
		if len(res) < 1 {
			logger.Errorf("%v", err)
		}
		return true, q.Summary, 0
	}
	for _, explain := range res {
		ragInfos := reGet(explain.OperatorInfo, "(json_type\\(|json_extract\\(|json_object\\(|json_array\\(|json_merge\\(|json_set\\(|json_insert\\(|json_replace\\(|json_remove\\(|abs\\(|floor\\(|ceiling\\()")
		if ragInfos != nil {
			pd := reGet(explain.Task, "(cop\\[tikv\\])")
			if pd != nil {
				return false, q.Summary, 0
			} else {
				return true, q.Summary, 0
			}
		}
	}
	return pass, q.Summary, 0
}

//RulePushdownToJoinOperator PUSHDOWN.002
func (q *Rule) RulePushdownToJoinOperator(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RulePushdownToJoinOperator")
	pass = true
	for _, tiStmt := range audit.TiStmt {
		switch node := tiStmt.(type) {
		case *ast.SelectStmt:
			pass = q.checkPD(node, pass)
			// pass = false 返回
			if pass {
				// 遍历所有子查询是否下推
				for _, selectStmt := range audit.SelectStmts {
					pass = q.checkPD(selectStmt.(*ast.SelectStmt), pass)
					// pass = false 结束循环
					if !pass {
						break
					}
				}
			}
		}
	}
	return pass, q.Summary, 0
}

func (q *Rule) checkPD(query *ast.SelectStmt, pass bool) bool {
	var resultSetNode ast.ResultSetNode
	if from := query.From; from != nil {
		_, resultSetNode = sql_util.PDSubQueryInFromClause(from.TableRefs)
		if tableSource, ok := resultSetNode.(*ast.TableSource); resultSetNode != nil && ok {
			// 如果没有别名,使用表名称,否则通过
			if tableSource.AsName.String() != "" {
				pass = sql_util.ThePredicatesIsNullExpr(tableSource.AsName, query.Where)
			} else {
				if name, ok := tableSource.Source.(*ast.TableName); ok {
					pass = sql_util.ThePredicatesIsNullExpr(name.Name, query.Where)
				}
			}
		}
	}
	return pass
}

//RulePushdownWithUserVaribales PUSHDOWN.003
func (q *Rule) RulePushdownWithUserVaribales(audit *Audit, info *task.DBInfo) (pass bool, newSummary string, affectRows int) {
	logger.Debugf("method : %s", "RulePushdownWithUserVaribales")

	pass = true
	for _, tiStmt := range audit.TiStmt {
		switch node := tiStmt.(type) {
		case *ast.SelectStmt:
			pass = q.IsVariableExpr(node, pass)
			// pass = false 返回
			if pass {
				for _, stmt := range audit.SelectStmts {
					switch query := stmt.(type) {
					case *ast.SelectStmt:
						pass = q.IsVariableExpr(query, pass)
						// pass = false 结束循环
						if !pass {
							break
						}
					}
				}
			}
		}
	}
	return pass, q.Summary, 0
}

func (q *Rule) IsVariableExpr(node *ast.SelectStmt, pass bool) bool {
	// select 子句
	for _, field := range node.Fields.Fields {
		if _, ok := field.Expr.(*ast.VariableExpr); ok {
			pass = false
			break
		}
	}
	// where 子句
	if node.Where != nil && pass {
		pass = sql_util.VariableExprInWhereClause(node.Where)
	}
	return pass
}
