package sql_util

import (
	"io/ioutil"
	"os"
	"strings"

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

const (
	separator = ";"
)

func SqlReadIn(in string) (error, []string) {

	var sqls []string

	err, buf := initQuery(in)
	if err != nil {
		return err, nil
	}
	buf = strings.TrimSpace(buf)

	// remove bom from file header
	buf, _ = util.RemoveBOM([]byte(buf))

	sqlCounter := 1
	var sql string

	// 处理所有 SQL 并返回
	for ; ; sqlCounter++ {
		if buf == "" {
			logger.Debugf("Ending, buf: '%s', sql: '%s'", buf, sql)
			break
		}

		// 查询请求切分
		orgSQL, sql, bufBytes := SplitStatement([]byte(buf), []byte(separator))
		if len(buf) == len(bufBytes) {
			// 防止切分死循环，当剩余的内容和原 SQL 相同时直接清空 buf
			buf = ""
			orgSQL = string(bufBytes)
			sql = orgSQL
		} else {
			buf = string(bufBytes)
		}

		// 去除无用的备注和空格
		sql = util.RemoveSQLComments(sql)
		if sql == "" {
			logger.Debugf("empty query or comment, buf: %s", buf)
			continue
		}
		logger.Debugf("main loop SQL: %s", sql)

		sqls = append(sqls, sql)
	}
	return nil, sqls
}

func initQuery(query string) (error, 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 {
	//		logger.Errorf("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 {
	//		logger.Errorf("ioutil.ReadAll Error: %v", err)
	//	}
	//	logger.Debug("initQuery get query from os.Stdin")
	//	return string(data)
	//}
	_, err := os.Stat(query)
	if err == nil {
		var data []byte
		data, err = ioutil.ReadFile(query)
		if err != nil {
			logger.Errorf("ioutil.ReadFile Error: %v", err)
		}
		logger.Debugf("initQuery get query from file: %s", query)
		return err, string(data)
	}

	return err, query
}

// SplitStatement SQL切分
// return 1. original sql, 2. remove comment sql, 3. left over buf
func SplitStatement(buf []byte, delimiter []byte) (string, string, []byte) {
	var singleLineComment bool
	var multiLineComment bool
	var quoted bool
	var quoteRune byte
	var sql string

	for i := 0; i < len(buf); i++ {
		b := buf[i]
		// single line comment
		if b == '-' {
			if !quoted && i+2 < len(buf) && buf[i+1] == '-' && buf[i+2] == ' ' {
				singleLineComment = true
				i = i + 2
				continue
			}
			if !quoted && i+2 < len(buf) && i == 0 && buf[i+1] == '-' && (buf[i+2] == '\n' || buf[i+2] == '\r') {
				sql = "--\n"
				break
			}
		}

		if b == '#' {
			if !multiLineComment && !quoted && !singleLineComment {
				singleLineComment = true
				continue
			}
		}

		// new line end single line comment
		if b == '\r' || b == '\n' {
			if singleLineComment {
				sql = string(buf[:i])
				singleLineComment = false
				if strings.HasPrefix(strings.TrimSpace(sql), "--") ||
					strings.HasPrefix(strings.TrimSpace(sql), "#") {
					// just comment, query start with '--', '#'
					break
				}
				// comment in multi-line sql
				continue
			}
			continue
		}

		// multi line comment
		// https://dev.mysql.com/doc/refman/8.0/en/comments.html
		// https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html
		if b == '/' && i+1 < len(buf) && buf[i+1] == '*' {
			if !multiLineComment && !singleLineComment && !quoted &&
				(buf[i+2] != '!' && buf[i+2] != '+') {
				i = i + 2
				multiLineComment = true
				continue
			}
		}

		if b == '*' && i+1 < len(buf) && buf[i+1] == '/' {
			if multiLineComment && !quoted && !singleLineComment {
				i = i + 2
				multiLineComment = false
				// '/*comment*/'
				if i == len(buf) {
					sql = string(buf[:i])
				}
				// '/*comment*/;', 'select 1/*comment*/;'
				if string(buf[i:]) == string(delimiter) {
					sql = string(buf)
				}
				continue
			}
		}

		// quoted string
		switch b {
		case '`', '\'', '"':
			if i > 1 && buf[i-1] != '\\' {
				if quoted && b == quoteRune {
					quoted = false
					quoteRune = 0
				} else {
					// check if first time found quote
					if quoteRune == 0 {
						quoted = true
						quoteRune = b
					}
				}
			}
		}

		// delimiter
		if !quoted && !singleLineComment && !multiLineComment {
			eof := true
			for k, c := range delimiter {
				if len(buf) > i+k && buf[i+k] != c {
					eof = false
				}
			}
			if eof {
				i = i + len(delimiter)
				sql = string(buf[:i])
				break
			}
		}

		// ended of buf
		if i == len(buf)-1 {
			sql = string(buf)
		}
	}
	orgSQL := string(buf[:len(sql)])
	buf = buf[len(sql):]
	return orgSQL, strings.TrimSuffix(sql, string(delimiter)), buf
}
