/*
 * 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 main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/XiaoMi/soar/advisor"
	"github.com/XiaoMi/soar/ast"
	"github.com/XiaoMi/soar/common"
	"github.com/XiaoMi/soar/database"
	"github.com/XiaoMi/soar/env"
)

// initConfig load config from default->file->cmdFlag
func initConfig() {
	// 更新 binary 文件所在路径为 BaseDir
	ex, err := os.Executable()
	if err != nil {
		panic(err)
	}
	common.BaseDir = filepath.Dir(ex)

	for i, c := range os.Args {
		// 如果指定了 -config, 它必须是第一个参数
		if strings.HasPrefix(c, "-config") && i != 1 {
			fmt.Println("-config must be the first arg")
			os.Exit(1)
		}
		// 等号两边请不要加空格
		if c == "=" {
			// -config = soar.yaml not support
			fmt.Println("wrong format, no space between '=', eg: -config=soar.yaml")
			os.Exit(1)
		}
	}

	// 加载配置文件，处理命令行参数
	err = common.ParseConfig(common.ArgConfig())
	// 检查配置文件及命令行参数是否正确
	if common.CheckConfig && err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
	}

	// 更新 HeuristicRules 中与配置相关的文字
	advisor.InitHeuristicRules()

	common.LogIfWarn(err, "")
}

// checkConfig for `-check-config` flag
// if error found return non-zero, no error return zero
func checkConfig() int {
	// TestDSN connection check
	connTest, err := database.NewConnector(common.Config.TestDSN)
	if err != nil {
		fmt.Println("test-dsn:", common.Config.TestDSN.Addr, err.Error())
		return 1
	}
	testVersion, err := connTest.Version()
	if err != nil && !common.Config.TestDSN.Disable {
		fmt.Println("test-dsn:", connTest, err.Error())
		return 1
	}
	if common.Config.Verbose {
		if err == nil {
			fmt.Println("test-dsn", connTest, "Version:", testVersion)
		} else {
			fmt.Println("test-dsn", common.Config.TestDSN)
		}
	}

	if !connTest.HasAllPrivilege() {
		fmt.Printf("test-dsn: %s, need all privileges", common.FormatDSN(common.Config.TestDSN))
		return 1
	}
	// OnlineDSN connection check
	connOnline, err := database.NewConnector(common.Config.OnlineDSN)
	if err != nil {
		fmt.Println("test-dsn:", common.Config.OnlineDSN.Addr, err.Error())
		return 1
	}
	onlineVersion, err := connOnline.Version()
	if err != nil && !common.Config.OnlineDSN.Disable {
		fmt.Println("online-dsn:", connOnline, err.Error())
		return 1
	}
	if common.Config.Verbose {
		if err == nil {
			fmt.Println("online-dsn", connOnline, "Version:", onlineVersion)
		} else {
			fmt.Println("online-dsn", common.Config.OnlineDSN)
		}
	}

	if !connOnline.HasSelectPrivilege() {
		fmt.Printf("online-dsn: %s, need all privileges", common.FormatDSN(common.Config.OnlineDSN))
		return 1
	}
	return 0
}

// helpTools help tools in cmd flags
func helpTools() (isContinue bool, exitCode int) {
	// environment error check, eg. MySQL password error
	if common.CheckConfig {
		return false, checkConfig()
	}
	// 打印 SOAR 版本信息
	if common.PrintVersion {
		common.SoarVersion()
		return false, 0
	}
	// 打印已加载配置的各配置项，检查配置是否生效
	if common.PrintConfig {
		common.PrintConfiguration()
		return false, 0
	}
	// 打印支持启发式建议
	if common.Config.ListHeuristicRules {
		advisor.ListHeuristicRules(advisor.HeuristicRules)
		return false, 0
	}
	// 打印支持的 SQL 重写规则
	if common.Config.ListRewriteRules {
		ast.ListRewriteRules(ast.RewriteRules)
		return false, 0
	}
	// 打印所有的测试 SQL
	if common.Config.ListTestSqls {
		advisor.ListTestSQLs()
		return false, 0
	}
	// 打印支持的 report-type
	if common.Config.ListReportTypes {
		common.ListReportTypes()
		return false, 0
	}

	return true, 0
}

// reportTool tools in report type
func reportTool(sql string, bom []byte) (isContinue bool, exitCode int) {
	switch common.Config.ReportType {
	case "html":
		// HTML 格式输入 CSS 加载
		fmt.Println(common.MarkdownHTMLHeader())
		return true, 0
	case "md2html":
		// markdown2html 转换小工具
		fmt.Println(common.MarkdownHTMLHeader())
		fmt.Println(common.Markdown2HTML(sql))
		return false, 0
	case "explain-digest":
		// 当用户输入为 EXPLAIN 信息，只对 Explain 信息进行分析
		// 注意： 这里只能处理一条 SQL 的 EXPLAIN 信息，用户一次反馈多条 SQL 的 EXPLAIN 信息无法处理
		advisor.DigestExplainText(sql)
		return false, 0
	case "chardet":
		// Get charset of input
		charset := common.CheckCharsetByBOM(bom)
		if charset == "" {
			charset = common.Chardet([]byte(sql))
		}
		fmt.Println(charset)
		return false, 0
	case "remove-comment":
		fmt.Println(database.RemoveSQLComments(sql))
		return false, 0
	default:
		return true, 0
	}
}

// initQuery
func initQuery(query string) string {
	// 读入待优化 SQL ，当配置文件或命令行参数未指定 SQL 时从管道读取
	if query == "" {
		// check stdin is pipe or terminal
		// https://stackoverflow.com/questions/22744443/check-if-there-is-something-to-read-on-stdin-in-golang
		stat, err := os.Stdin.Stat()
		if stat == nil {
			common.Log.Critical("os.Stdin.Stat Error: %v", err)
			os.Exit(1)
		}
		if (stat.Mode() & os.ModeCharDevice) != 0 {
			// stdin is from a terminal
			fmt.Println("Args format error, use --help see how to use it!")
			os.Exit(1)
		}
		// read from pipe
		var data []byte
		data, err = ioutil.ReadAll(os.Stdin)
		if err != nil {
			common.Log.Critical("ioutil.ReadAll Error: %v", err)
		}
		common.Log.Debug("initQuery get query from os.Stdin")
		return string(data)
	}

	if _, err := os.Stat(query); err == nil {
		var data []byte
		data, err = ioutil.ReadFile(query)
		if err != nil {
			common.Log.Critical("ioutil.ReadFile Error: %v", err)
		}
		common.Log.Debug("initQuery get query from file: %s", query)
		return string(data)
	}

	return query
}

func shutdown(vEnv *env.VirtualEnv, rEnv *database.Connector) {
	if common.Config.DropTestTemporary {
		vEnv.CleanUp()
	}
	err := vEnv.Conn.Close()
	common.LogIfWarn(err, "")
	err = rEnv.Conn.Close()
	common.LogIfWarn(err, "")
	os.Exit(0)
}

func verboseInfo() {
	if !common.Config.Verbose {
		return
	}
	// syntax check verbose mode, add output for success!
	if common.Config.OnlySyntaxCheck {
		fmt.Println("Syntax check OK!")
		return
	}
	switch common.Config.ReportType {
	case "markdown":
		if common.Config.TestDSN.Disable || common.Config.OnlineDSN.Disable {
			fmt.Println("MySQL environment verbose info")
			// TestDSN
			if common.Config.TestDSN.Disable {
				fmt.Println("* test-dsn:", common.Config.TestDSN.Addr, "is disable, please check log.")
			}
			// OnlineDSN
			if common.Config.OnlineDSN.Disable {
				fmt.Println("* online-dsn:", common.Config.OnlineDSN.Addr, "is disable, please check log.")
			}
		}
	}
}
