package tool_handler

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/xinliangnote/go-gin-api/internal/pkg/code"
	"github.com/xinliangnote/go-gin-api/internal/pkg/core"
	"github.com/xinliangnote/go-gin-api/pkg/errno"

	"github.com/spf13/cast"
)

type tableColumn struct {
	ColumnName    string `json:"column_name"`    // 字段名称
	ColumnComment string `json:"column_comment"` // 字段注释
}

type searchMySQLRequest struct {
	DbName    string `form:"db_name"`    // 数据库名称
	TableName string `form:"table_name"` // 数据表名称
	SQL       string `form:"sql"`        // SQL 语句
}

type searchMySQLResponse struct {
	Cols     []string                 `json:"cols"`      // 查询后的行
	ColsInfo []tableColumn            `json:"cols_info"` // 行信息
	List     []map[string]interface{} `json:"list"`      // 查询后的数据
}

var preFilterList = map[string]bool{
	"insert": true,
	"update": true,
	"delete": true,
	"create": true,
	"source": true,
	"rename": true,
}

var whiteListKeyword = []string{
	"is_deleted",
	"updated_at",
	"created_at",
	"updated_user",
	"created_user",
	"show create table",
}

var filterListKeyword = []string{
	"insert",
	"update",
	"truncate",
	"delete",
	"create",
	"alter",
	"rename",
	"drop",
	"replace",
	"sleep",
	"grant",
	"revoke",
	"load_file",
	"outfile",
	"transaction",
	"commit",
	"mysqldump",
	"into",
}

// SearchMySQL 执行 SQL 语句
// @Summary 执行 SQL 语句
// @Description 执行 SQL 语句
// @Tags API.tool
// @Accept multipart/form-data
// @Produce json
// @Param db_name formData string true "数据库名称"
// @Param table_name formData string true "数据表名称"
// @Param sql formData string true "SQL 语句"
// @Success 200 {object} searchMySQLResponse
// @Failure 400 {object} code.Failure
// @Router /api/tool/data/mysql [post]
func (h *handler) SearchMySQL() core.HandlerFunc {
	return func(c core.Context) {
		req := new(searchMySQLRequest)
		res := new(searchMySQLResponse)
		if err := c.ShouldBindForm(req); err != nil {
			c.AbortWithError(errno.NewError(
				http.StatusBadRequest,
				code.ParamBindError,
				code.Text(code.ParamBindError)).WithErr(err),
			)
			return
		}

		sql := strings.ToLower(strings.TrimSpace(req.SQL))
		if sql == "" {
			c.AbortWithError(errno.NewError(
				http.StatusBadRequest,
				code.MySQLExecError,
				"SQL 语句不能为空！"),
			)
			return
		}

		if preFilterList[string([]byte(sql)[:6])] {
			c.AbortWithError(errno.NewError(
				http.StatusBadRequest,
				code.MySQLExecError,
				"SQL 语句不能以 "+string([]byte(sql)[:6])+" 开头！"),
			)
			return
		}

		for _, f := range filterListKeyword {
			if find := strings.Contains(sql, f); find {

				isWhiteList := false
				for _, w := range whiteListKeyword {
					if whiteFind := strings.Contains(sql, w); whiteFind {
						isWhiteList = true
						break
					}
				}

				if !isWhiteList {
					c.AbortWithError(errno.NewError(
						http.StatusBadRequest,
						code.MySQLExecError,
						"SQL 语句存在敏感词： "+f+"！"),
					)
					return
				}

			}
		}

		if strings.ToLower(string([]byte(sql)[:6])) == "select" {
			sql += " LIMIT 100"
		}

		// TODO 后期支持查询多个数据库
		rows, err := h.db.GetDbR().Raw(sql).Rows()
		if err != nil {
			c.AbortWithError(errno.NewError(
				http.StatusBadRequest,
				code.MySQLExecError,
				"MySQL "+err.Error()).WithErr(err),
			)
			return
		}

		defer rows.Close()

		cols, _ := rows.Columns()

		var data []map[string]interface{}

		for rows.Next() {
			// Create a slice of interface{}'s to represent each column,
			// and a second slice to contain pointers to each item in the columns slice.
			columns := make([]interface{}, len(cols))
			columnPointers := make([]interface{}, len(cols))
			for i := range columns {
				columnPointers[i] = &columns[i]
			}

			// Scan the result into the column pointers...
			if err := rows.Scan(columnPointers...); err != nil {
				fmt.Printf("query table scan error, detail is [%v]\n", err.Error())
				continue
			}

			// Create our map, and retrieve the value for each column from the pointers slice,
			// storing it in the map with the name of the column as the key.
			m := make(map[string]interface{})
			for i, colName := range cols {
				val := columnPointers[i].(*interface{})
				m[colName] = cast.ToString(*val)
			}

			data = append(data, m)

		}

		res.List = data
		res.Cols = cols

		sqlTableColumn := fmt.Sprintf("SELECT `COLUMN_NAME`, `COLUMN_COMMENT` FROM `information_schema`.`columns` WHERE `table_schema`= '%s' AND `table_name`= '%s' ORDER BY `ORDINAL_POSITION` ASC",
			req.DbName, req.TableName)

		rows, err = h.db.GetDbR().Raw(sqlTableColumn).Rows()
		if err != nil {
			c.AbortWithError(errno.NewError(
				http.StatusBadRequest,
				code.MySQLExecError,
				"MySQL "+err.Error()).WithErr(err),
			)
			return
		}
		defer rows.Close()

		var tableColumns []tableColumn

		for rows.Next() {
			var column tableColumn
			err = rows.Scan(
				&column.ColumnName,
				&column.ColumnComment)

			if err != nil {
				fmt.Printf("query table column scan error, detail is [%v]\n", err.Error())
				continue
			}

			tableColumns = append(tableColumns, column)
		}

		res.ColsInfo = tableColumns

		c.Payload(res)
	}
}
