/*
 * Copyright 2018 Xiaomi, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package advisor

import (
	"fmt"
	"strings"

	"github.com/XiaoMi/soar/common"
	"github.com/XiaoMi/soar/database"
)

var explainRuleID int

// [EXP.XXX]Rule
var explainRules map[string]Rule

// [table_name]"suggest text"
var tablesSuggests map[string][]string

/*
var explainIgnoreTables = []string{
	"dual",
	"",
}
*/

// explain建议的形式
// Item: EXP.XXX
// Severity: L[0-8]
// Summary: full table scan, not use index, full index scan...
// Content: XX TABLE xxx

//
func checkExplainSelectType(exp *database.ExplainInfo) {
	// 判断是否跳过不检查
	if len(common.Config.ExplainWarnSelectType) == 1 {
		if common.Config.ExplainWarnSelectType[0] == "" {
			return
		}
	} else if len(common.Config.ExplainWarnSelectType) < 1 {
		return
	}

	if exp.ExplainFormat == database.JSONFormatExplain {
		// TODO
		// JSON形式遍历分析不方便，转成Row格式也没有SelectType暂不处理
		return
	}
	for _, v := range common.Config.ExplainWarnSelectType {
		for _, row := range exp.ExplainRows {
			if row.SelectType == v && v != "" {
				tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("SelectType:%s", row.SelectType))
			}
		}
	}
}

// 用户可以设置AccessType的建议级别，匹配到的查询会给出建议
func checkExplainAccessType(exp *database.ExplainInfo) {
	// 判断是否跳过不检查
	if len(common.Config.ExplainWarnAccessType) == 1 {
		if common.Config.ExplainWarnAccessType[0] == "" {
			return
		}
	} else if len(common.Config.ExplainWarnAccessType) < 1 {
		return
	}

	rows := exp.ExplainRows
	if exp.ExplainFormat == database.JSONFormatExplain {
		// JSON形式遍历分析不方便，转成Row格式统一处理
		rows = database.ConvertExplainJSON2Row(exp.ExplainJSON)
	}
	for _, v := range common.Config.ExplainWarnAccessType {
		for _, row := range rows {
			if row.AccessType == v && v != "" {
				tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("Scalability:%s", row.Scalability))
			}
		}
	}
}

// TODO:
/*
func checkExplainPossibleKeys(exp *database.ExplainInfo) {
	// 判断是否跳过不检查
	if common.Config.ExplainMinPossibleKeys == 0 {
		return
	}

	rows := exp.ExplainRows
	if exp.ExplainFormat == database.JSONFormatExplain {
		// JSON形式遍历分析不方便，转成Row格式统一处理
		rows = database.ConvertExplainJSON2Row(exp.ExplainJSON)
	}
	for _, row := range rows {
		if len(row.PossibleKeys) < common.Config.ExplainMinPossibleKeys {
			tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("PossibleKeys:%d < %d",
				len(row.PossibleKeys), common.Config.ExplainMinPossibleKeys))
		}
	}
}
*/

// TODO:
/*
func checkExplainKeyLen(exp *database.ExplainInfo) {
}
*/

// TODO:
/*
func checkExplainKey(exp *database.ExplainInfo) {
	   // 小于最小使用试用key数量
	   //return intval($explainResult) < intval($userCond);
	   //explain-min-keys int
}
*/

func checkExplainRef(exp *database.ExplainInfo) {
	rows := exp.ExplainRows
	if exp.ExplainFormat == database.JSONFormatExplain {
		// JSON形式遍历分析不方便，转成Row格式统一处理
		rows = database.ConvertExplainJSON2Row(exp.ExplainJSON)
	}
	for i, row := range rows {
		if strings.Join(row.Ref, "") == "NULL" || strings.Join(row.Ref, "") == "" {
			if i == 0 && len(rows) > 1 {
				continue
			}
			tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("Ref:null"))
		}
	}
}

func checkExplainRows(exp *database.ExplainInfo) {
	// 判断是否跳过不检查
	if common.Config.ExplainMaxRows <= 0 {
		return
	}

	rows := exp.ExplainRows
	if exp.ExplainFormat == database.JSONFormatExplain {
		// JSON形式遍历分析不方便，转成Row格式统一处理
		rows = database.ConvertExplainJSON2Row(exp.ExplainJSON)
	}

	for _, row := range rows {
		if row.Rows >= common.Config.ExplainMaxRows {
			tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("Rows:%d", row.Rows))
		}
	}
}

// TODO:
/*
func checkExplainExtra(exp *database.ExplainInfo) {
	   // 包含用户配置的逗号分隔关键词之一则提醒
	   // return self::contains($explainResult, $userCond);
	   // explain-warn-extra []string
}
*/

func checkExplainFiltered(exp *database.ExplainInfo) {
	// 判断是否跳过不检查
	if common.Config.ExplainMaxFiltered <= 0.001 {
		return
	}

	rows := exp.ExplainRows
	if exp.ExplainFormat == database.JSONFormatExplain {
		// JSON形式遍历分析不方便，转成Row格式统一处理
		rows = database.ConvertExplainJSON2Row(exp.ExplainJSON)
	}
	for i, row := range rows {
		if i == 0 && len(rows) > 1 {
			continue
		}
		if row.Filtered >= common.Config.ExplainMaxFiltered {
			tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("Filtered:%.2f%s", row.Filtered, "%"))
		}
	}
}

// ExplainAdvisor 基于explain信息给出建议
func ExplainAdvisor(exp *database.ExplainInfo) map[string]Rule {
	common.Log.Debug("ExplainAdvisor SQL: %v", exp.SQL)
	explainRuleID = 0
	explainRules = make(map[string]Rule)
	tablesSuggests = make(map[string][]string)

	checkExplainSelectType(exp)
	checkExplainAccessType(exp)
	checkExplainFiltered(exp)
	checkExplainRef(exp)
	checkExplainRows(exp)

	// 打印explain table
	content := database.PrintMarkdownExplainTable(exp)

	if common.Config.ShowWarnings {
		content += "\n" + database.MySQLExplainWarnings(exp)
	}

	// 对explain table中各项难于理解的值做解释
	cases := database.ExplainInfoTranslator(exp)

	// 添加last_query_cost
	if common.Config.ShowLastQueryCost {
		content += "\n" + database.MySQLExplainQueryCost(exp)
	}

	if content != "" {
		explainRules["EXP.000"] = Rule{
			Item:     "EXP.000",
			Severity: "L0",
			Summary:  "Explain信息",
			Content:  content,
			Case:     cases,
			Func:     (*Query4Audit).RuleOK,
		}
	}
	/*
		for t, s := range tablesSuggests {
			// 检查explain对应的表是否需要跳过，如dual,空表等
			ig := false
			for _, ti := range explainIgnoreTables {
				if ti == t {
					ig = true
				}
			}
			if ig {
				continue
			}
			ruleId := fmt.Sprintf("EXP.%03d", explainRuleId+1)
			explainRuleId = explainRuleId + 1
			explainRules[ruleId] = Rule{
				Item:     ruleId,
				Severity: "L0",
				Summary:  fmt.Sprintf("表 `%s` 查询效率不高", t),
				Content:  fmt.Sprint("原因：", strings.Join(s, ",")),
				Case:     "",
				Func:     (*Query4Audit).RuleOK,
			}
		}
	*/
	return explainRules
}

// DigestExplainText 分析用户输入的EXPLAIN信息
func DigestExplainText(text string) {
	// explain信息就不要显示完美了，美不美自己看吧。
	common.Config.IgnoreRules = append(common.Config.IgnoreRules, "OK")

	if !IsIgnoreRule("EXP.") {
		explainInfo, err := database.ParseExplainText(text)
		if err != nil {
			common.Log.Error("main ParseExplainText Error: %v", err)
			return
		}
		expSuggest := ExplainAdvisor(explainInfo)
		_, output := FormatSuggest("", common.Config.ReportType, expSuggest)
		if common.Config.ReportType == "html" {
			fmt.Println(common.MarkdownHTMLHeader())
			fmt.Println(common.Markdown2HTML(output))
		} else {
			fmt.Println(output)
		}
	}
}
