package checker

import (
	"fmt"

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

	"vitess.io/vitess/go/vt/sqlparser"

	"github.com/pingcap/tispector/service/task"
	"github.com/pingcap/tispector/util"
	"github.com/pingcap/tispector/util/logger"
)

// Audit 待评审的SQL结构体，由原SQL和其对应的抽象语法树组成
type Audit struct {
	Query       string              // 查询语句
	Stmt        sqlparser.Statement // 通过Vitess解析出的抽象语法树
	TiStmt      []ast.StmtNode      // 通过TiDB解析出的抽象语法树
	SelectStmts []ast.ResultSetNode // 通过TiDB解析出的 select 抽象语法树,再次解析出的 select 中的子查询，目前只有 select,from,where 子句的子查询
}

type CheckerService struct {
}

var Checker CheckerService

// Results 结果集，sql语句可能匹配到不止一种规则
type Result struct {
	Pass       bool
	Suggestion string
	AffectRow  int
	ItemName   string
}

// SqlTotCheck 遍历所有规则，返回所有匹配项
func (CheckerService) SqlTotCheck(sql, charset, collation string, info *task.DBInfo) []Result {
	audit, err := NewAudit(sql, charset, collation)
	var results []Result
	var result Result
	if err != nil {
		return []Result{{false, fmt.Sprintf("sql解析错误：%s", err.Error()), 0, ""}}
	}

	for _, v := range Rules {

		result.Pass, result.Suggestion, result.AffectRow = v.CheckFunc(&v, audit, info)
		result.ItemName = v.Item
		if !result.Pass {
			// fmt.Println(v.Item)
			results = append(results, result)
		}
	}

	if len(results) == 0 {
		results = append(results, Result{true, "未命中任何已有规则", 0, ""})
	}
	return results
}

// SqlTotCheck 遍历特定规则，返回所有匹配项
func (CheckerService) SqlTotCheckByRuleItems(sql, charset, collation string, info *task.DBInfo, RuleItems []string) []Result {
	audit, err := NewAudit(sql, charset, collation)
	var results []Result
	var result Result
	if err != nil {
		return []Result{{false, fmt.Sprintf("sql解析错误：%s", err.Error()), 0, ""}}
	}
	var neededRules []Rule
	for _, v := range Rules {
		v.Close = false
		for _, Item := range RuleItems {
			if v.Item == Item {
				neededRules = append(neededRules, v)
			}
		}
	}

	for _, v := range neededRules {

		result.Pass, result.Suggestion, result.AffectRow = v.CheckFunc(&v, audit, info)
		result.ItemName = v.Item
		if !result.Pass {
			// fmt.Println(v.Item)
			results = append(results, result)
		}
	}

	if len(results) == 0 {
		results = append(results, Result{true, "未命中任何已有规则", 0, ""})
	}
	return results
}

// SqlCheck 遍历规则，返回第一个匹配项
func (CheckerService) SqlCheck(sql, charset, collation string, info *task.DBInfo) (pass bool, suggestion string, affectRow int) {
	audit, err := NewAudit(sql, charset, collation)
	if err != nil {
		return false, fmt.Sprintf("sql解析错误：%s", err.Error()), 0
	}

	pass = true

	for _, v := range Rules {
		if v.Close {
			continue
		}

		pass, suggestion, affectRow = v.CheckFunc(&v, audit, info)

		if !pass {
			break
		}
	}

	return pass, suggestion, affectRow
}

func (CheckerService) ListRules() interface{} {
	return Rules
}

// NewQuery4Audit return a struct for Audit
func NewAudit(sql, charset, collation string) (*Audit, error) {
	q := &Audit{Query: sql}
	// vitess 语法解析不上报，以 tidb parser 为主
	q.Stmt, _ = sqlparser.Parse(sql)
	// tdib parser 语法解析
	var warns []error
	var err error
	sqlInfo, err := GetSQLInfo(sql)
	if err == nil {
		for columnName, columnType := range sqlInfo.ColumnTypes {
			fmt.Println(columnName, columnType)
		}
	}
	q.TiStmt, warns, err = parser.New().Parse(sql, charset, collation)
	if len(warns) > 0 {
		logger.Warn("parse sql warning: ", util.ErrsJoin("; ", warns))
	}

	for _, tiStmt := range q.TiStmt {
		switch query := tiStmt.(type) {
		case *ast.SelectStmt:
			q.SelectStmts = sql_util.GetAllClause(query, q.SelectStmts)
		case *ast.InsertStmt:
			if ins, ok := query.Select.(*ast.SelectStmt); ok {
				q.SelectStmts = sql_util.GetAllClause(ins, q.SelectStmts)
			}
		case *ast.UpdateStmt:
			q.SelectStmts = sql_util.GetSubQueryInFromClause(query.TableRefs.TableRefs, q.SelectStmts)
			q.SelectStmts = sql_util.GetSubQueryInWhereClause(query.Where, q.SelectStmts)
			for _, assignment := range query.List {
				q.SelectStmts = sql_util.GetSubQueryInWhereClause(assignment.Expr, q.SelectStmts)
			}
		case *ast.DeleteStmt:
			q.SelectStmts = sql_util.GetSubQueryInFromClause(query.TableRefs.TableRefs, q.SelectStmts)
			q.SelectStmts = sql_util.GetSubQueryInWhereClause(query.Where, q.SelectStmts)
		}

	}

	return q, err
}
