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

import (
	"fmt"
	"regexp"
	"strconv"
	"strings"
	"time"

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

// SHOW TABLE STATUS Syntax
// https://dev.mysql.com/doc/refman/5.7/en/show-table-status.html

// TableStatInfo 用以保存 show table status 之后获取的table信息
type TableStatInfo struct {
	Name string
	Rows []tableStatusRow
}

// tableStatusRow 用于 show table status value
type tableStatusRow struct {
	Name         string // 表名
	Engine       string // 该表使用的存储引擎
	Version      int    // 该表的 .frm 文件版本号
	RowFormat    string // 该表使用的行存储格式
	Rows         int64  // 表行数, InnoDB 引擎中为预估值，甚至可能会有40%~50%的数值偏差
	AvgRowLength int    // 平均行长度

	// MyISAM: Data_length 为数据文件的大小，单位为 bytes
	// InnoDB: Data_length 为聚簇索引分配的近似内存量，单位为 bytes, 计算方式为聚簇索引数量乘以 InnoDB 页面大小
	// 其他不同的存储引擎中该值的意义可能不尽相同
	DataLength int

	// MyISAM: Max_data_length 为数据文件长度的最大值。这是在给定使用的数据指针大小的情况下，可以存储在表中的数据的最大字节数
	// InnoDB: 未使用
	// 其他不同的存储引擎中该值的意义可能不尽相同
	MaxDataLength int

	// MyISAM: Index_length 为 index 文件的大小，单位为 bytes
	// InnoDB: Index_length 为非聚簇索引分配的近似内存量，单位为 bytes，计算方式为非聚簇索引数量乘以 InnoDB 页面大小
	// 其他不同的存储引擎中该值的意义可能不尽相同
	IndexLength int

	DataFree      int       // 已分配但未使用的字节数
	AutoIncrement int       // 下一个自增值
	CreateTime    time.Time // 创建时间
	UpdateTime    time.Time // 最近一次更新时间，该值不准确
	CheckTime     time.Time // 上次检查时间
	Collation     string    // 字符集及排序规则信息
	Checksum      string    // 校验和
	CreateOptions string    // 创建表的时候的时候一切其他属性
	Comment       string    // 注释
}

// newTableStat 构造 table Stat 对象
func newTableStat(tableName string) *TableStatInfo {
	return &TableStatInfo{
		Name: tableName,
		Rows: make([]tableStatusRow, 0),
	}
}

// ShowTables 执行 show tables
func (db *Connector) ShowTables() ([]string, error) {
	defer func() {
		err := recover()
		if err != nil {
			common.Log.Error("recover ShowTableStatus()", err)
		}
	}()

	// 执行 show table status
	res, err := db.Query("show tables")
	if err != nil {
		return []string{}, err
	}

	// 获取值
	var tables []string
	for _, row := range res.Rows {
		tables = append(tables, row.Str(0))
	}

	return tables, err
}

// ShowTableStatus 执行 show table status
func (db *Connector) ShowTableStatus(tableName string) (*TableStatInfo, error) {
	defer func() {
		err := recover()
		if err != nil {
			common.Log.Error("recover ShowTableStatus()", err)
		}
	}()

	// 初始化struct
	ts := newTableStat(tableName)

	// 执行 show table status
	res, err := db.Query("show table status where name = '%s'", ts.Name)
	if err != nil {
		return ts, err
	}

	rs := res.Result.Map("Rows")
	name := res.Result.Map("Name")
	df := res.Result.Map("Data_free")
	sum := res.Result.Map("Checksum")
	engine := res.Result.Map("Engine")
	version := res.Result.Map("Version")
	comment := res.Result.Map("Comment")
	ai := res.Result.Map("Auto_increment")
	collation := res.Result.Map("Collation")
	rowFormat := res.Result.Map("Row_format")
	checkTime := res.Result.Map("Check_time")
	dataLength := res.Result.Map("Data_length")
	idxLength := res.Result.Map("Index_length")
	createTime := res.Result.Map("Create_time")
	updateTime := res.Result.Map("Update_time")
	options := res.Result.Map("Create_options")
	avgRowLength := res.Result.Map("Avg_row_length")
	maxDataLength := res.Result.Map("Max_data_length")

	// 获取值
	for _, row := range res.Rows {
		value := tableStatusRow{
			Name:          row.Str(name),
			Engine:        row.Str(engine),
			Version:       row.Int(version),
			Rows:          row.Int64(rs),
			RowFormat:     row.Str(rowFormat),
			AvgRowLength:  row.Int(avgRowLength),
			DataLength:    row.Int(dataLength),
			MaxDataLength: row.Int(maxDataLength),
			IndexLength:   row.Int(idxLength),
			DataFree:      row.Int(df),
			AutoIncrement: row.Int(ai),
			CreateTime:    row.Time(createTime, time.Local),
			UpdateTime:    row.Time(updateTime, time.Local),
			CheckTime:     row.Time(checkTime, time.Local),
			Collation:     row.Str(collation),
			Checksum:      row.Str(sum),
			CreateOptions: row.Str(options),
			Comment:       row.Str(comment),
		}
		ts.Rows = append(ts.Rows, value)
	}

	return ts, err
}

// https://dev.mysql.com/doc/refman/5.7/en/show-index.html

// TableIndexInfo 用以保存 show index 之后获取的 index 信息
type TableIndexInfo struct {
	TableName string
	IdxRows   []TableIndexRow
}

// TableIndexRow 用以存放show index之后获取的每一条index信息
type TableIndexRow struct {
	Table        string // 表名
	NonUnique    int    // 0：unique key，1：not unique
	KeyName      string // index的名称，如果是主键则为 "PRIMARY"
	SeqInIndex   int    // 该列在索引中的位置。计数从 1 开始
	ColumnName   string // 列名
	Collation    string // A or Null
	Cardinality  int    // 索引中唯一值的数量，"ANALYZE TABLE" 可更新该值
	SubPart      int    // 索引前缀字节数
	Packed       int
	Null         string // 表示该列是否可以为空，如果可以为 'YES'，反之''
	IndexType    string // BTREE, FULLTEXT, HASH, RTREE
	Comment      string
	IndexComment string
}

// NewTableIndexInfo 构造 TableIndexInfo
func NewTableIndexInfo(tableName string) *TableIndexInfo {
	return &TableIndexInfo{
		TableName: tableName,
		IdxRows:   make([]TableIndexRow, 0),
	}
}

// ShowIndex show Index
func (db *Connector) ShowIndex(tableName string) (*TableIndexInfo, error) {
	tbIndex := NewTableIndexInfo(tableName)

	// 执行 show create table
	res, err := db.Query("show index from `%s`.`%s`", db.Database, tableName)
	if err != nil {
		return nil, err
	}

	table := res.Result.Map("Table")
	unique := res.Result.Map("Non_unique")
	keyName := res.Result.Map("Key_name")
	seq := res.Result.Map("Seq_in_index")
	cName := res.Result.Map("Column_name")
	collation := res.Result.Map("Collation")
	cardinality := res.Result.Map("Cardinality")
	subPart := res.Result.Map("Sub_part")
	packed := res.Result.Map("Packed")
	null := res.Result.Map("Null")
	idxType := res.Result.Map("Index_type")
	comment := res.Result.Map("Comment")
	idxComment := res.Result.Map("Index_comment")

	// 获取值
	for _, row := range res.Rows {
		value := TableIndexRow{
			Table:        row.Str(table),
			NonUnique:    row.Int(unique),
			KeyName:      row.Str(keyName),
			SeqInIndex:   row.Int(seq),
			ColumnName:   row.Str(cName),
			Collation:    row.Str(collation),
			Cardinality:  row.Int(cardinality),
			SubPart:      row.Int(subPart),
			Packed:       row.Int(packed),
			Null:         row.Str(null),
			IndexType:    row.Str(idxType),
			Comment:      row.Str(comment),
			IndexComment: row.Str(idxComment),
		}
		tbIndex.IdxRows = append(tbIndex.IdxRows, value)
	}
	return tbIndex, err
}

// IndexSelectKey 用以对 TableIndexInfo 进行查询
type IndexSelectKey string

// 索引相关
const (
	IndexKeyName    = IndexSelectKey("KeyName")    // 索引名称
	IndexColumnName = IndexSelectKey("ColumnName") // 索引列名称
	IndexIndexType  = IndexSelectKey("IndexType")  // 索引类型
	IndexNonUnique  = IndexSelectKey("NonUnique")  // 唯一索引
)

// FindIndex 获取TableIndexInfo中需要的索引
func (tbIndex *TableIndexInfo) FindIndex(arg IndexSelectKey, value string) []TableIndexRow {
	var result []TableIndexRow
	if tbIndex == nil {
		return result
	}

	value = strings.ToLower(value)

	switch arg {
	case IndexKeyName:
		for _, index := range tbIndex.IdxRows {
			if strings.ToLower(index.KeyName) == value {
				result = append(result, index)
			}
		}

	case IndexColumnName:
		for _, index := range tbIndex.IdxRows {
			if strings.ToLower(index.ColumnName) == value {
				result = append(result, index)
			}
		}

	case IndexIndexType:
		for _, index := range tbIndex.IdxRows {
			if strings.ToLower(index.IndexType) == value {
				result = append(result, index)
			}
		}

	case IndexNonUnique:
		for _, index := range tbIndex.IdxRows {
			unique := strconv.Itoa(index.NonUnique)
			if unique == value {
				result = append(result, index)
			}
		}

	default:
		common.Log.Error("no such args: TableIndexRow")
	}

	return result
}

// desc table
// https://dev.mysql.com/doc/refman/5.7/en/show-columns.html

// TableDesc show columns from rental;
type TableDesc struct {
	Name       string
	DescValues []TableDescValue
}

// TableDescValue 含有每一列的属性
type TableDescValue struct {
	Field      string // 列名
	Type       string // 数据类型
	Null       string // 是否有NULL（NO、YES）
	Collation  string // 字符集
	Privileges string // 权限s
	Key        string // 键类型
	Default    string // 默认值
	Extra      string // 其他
	Comment    string // 备注
}

// NewTableDesc 初始化一个*TableDesc
func NewTableDesc(tableName string) *TableDesc {
	return &TableDesc{
		Name:       tableName,
		DescValues: make([]TableDescValue, 0),
	}
}

// ShowColumns 获取 DB 中所有的 columns
func (db *Connector) ShowColumns(tableName string) (*TableDesc, error) {
	tbDesc := NewTableDesc(tableName)

	// 执行 show create table
	res, err := db.Query("show full columns from `%s`.`%s`", db.Database, tableName)
	if err != nil {
		return nil, err
	}

	field := res.Result.Map("Field")
	tp := res.Result.Map("Type")
	null := res.Result.Map("Null")
	key := res.Result.Map("Key")
	def := res.Result.Map("Default")
	extra := res.Result.Map("Extra")
	collation := res.Result.Map("Collation")
	privileges := res.Result.Map("Privileges")
	comm := res.Result.Map("Comment")

	// 获取值
	for _, row := range res.Rows {
		value := TableDescValue{
			Field:      row.Str(field),
			Type:       row.Str(tp),
			Null:       row.Str(null),
			Key:        row.Str(key),
			Default:    row.Str(def),
			Extra:      row.Str(extra),
			Privileges: row.Str(privileges),
			Collation:  row.Str(collation),
			Comment:    row.Str(comm),
		}
		tbDesc.DescValues = append(tbDesc.DescValues, value)
	}
	return tbDesc, err
}

// Columns 用于获取TableDesc中所有列的名称
func (td TableDesc) Columns() []string {
	var cols []string
	for _, col := range td.DescValues {
		cols = append(cols, col.Field)
	}
	return cols
}

// showCreate show create
func (db *Connector) showCreate(createType, name string) (string, error) {
	// 执行 show create table
	res, err := db.Query("show create %s `%s`", createType, name)
	if err != nil {
		return "", err
	}

	// 获取ddl
	var ddl string
	for _, row := range res.Rows {
		ddl = row.Str(1)
	}

	return ddl, err
}

// ShowCreateDatabase show create database
func (db *Connector) ShowCreateDatabase(dbName string) (string, error) {
	defer func() {
		err := recover()
		if err != nil {
			common.Log.Error("recover ShowCreateDatabase()", err)
		}
	}()
	return db.showCreate("database", dbName)
}

// ShowCreateTable show create table
func (db *Connector) ShowCreateTable(tableName string) (string, error) {
	defer func() {
		err := recover()
		if err != nil {
			common.Log.Error("recover ShowCreateTable()", err)
		}
	}()

	ddl, err := db.showCreate("table", tableName)

	// 去除外键关联条件
	var noConstraint []string
	relationReg, _ := regexp.Compile("CONSTRAINT")
	for _, line := range strings.Split(ddl, "\n") {

		if relationReg.Match([]byte(line)) {
			continue
		}

		// 去除外键语句会使DDL中多一个','导致语法错误，要把多余的逗号去除
		if strings.Index(line, ")") == 0 {
			lineWrongSyntax := noConstraint[len(noConstraint)-1]
			// 如果')'前一句的末尾是',' 删除 ',' 保证语法正确性
			if strings.Index(lineWrongSyntax, ",") == len(lineWrongSyntax)-1 {
				noConstraint[len(noConstraint)-1] = lineWrongSyntax[:len(lineWrongSyntax)-1]
			}
		}

		noConstraint = append(noConstraint, line)
	}

	return strings.Join(noConstraint, "\n"), err
}

// FindColumn find column
func (db *Connector) FindColumn(name, dbName string, tables ...string) ([]*common.Column, error) {
	// 执行 show create table
	var columns []*common.Column
	sql := fmt.Sprintf("SELECT "+
		"c.TABLE_NAME,c.TABLE_SCHEMA,c.COLUMN_TYPE,c.CHARACTER_SET_NAME, c.COLLATION_NAME "+
		"FROM `INFORMATION_SCHEMA`.`COLUMNS` as c where c.COLUMN_NAME = '%s' ", name)

	if len(tables) > 0 {
		var tmp []string
		for _, table := range tables {
			tmp = append(tmp, "'"+table+"'")
		}
		sql += fmt.Sprintf(" and c.table_name in (%s)", strings.Join(tmp, ","))
	}

	if dbName != "" {
		sql += fmt.Sprintf(" and c.table_schema = '%s'", dbName)
	}

	res, err := db.Query(sql)
	if err != nil {
		common.Log.Error("(db *Connector) FindColumn Error : ", err)
		return columns, err
	}

	tbName := res.Result.Map("TABLE_NAME")
	schema := res.Result.Map("TABLE_SCHEMA")
	colTyp := res.Result.Map("COLUMN_TYPE")
	colCharset := res.Result.Map("CHARACTER_SET_NAME")
	collation := res.Result.Map("COLLATION_NAME")

	// 获取ddl
	for _, row := range res.Rows {
		col := &common.Column{
			Name:      name,
			Table:     row.Str(tbName),
			DB:        row.Str(schema),
			DataType:  row.Str(colTyp),
			Character: row.Str(colCharset),
			Collation: row.Str(collation),
		}

		// 填充字符集和排序规则
		if col.Character == "" {
			// 当从`INFORMATION_SCHEMA`.`COLUMNS`表中查询不到相关列的character和collation的信息时
			// 认为该列使用的character和collation与其所处的表一致
			// 由于`INFORMATION_SCHEMA`.`TABLES`表中未找到表的character，所以从按照MySQL中collation的规则从中截取character

			sql = fmt.Sprintf("SELECT `t`.`TABLE_COLLATION` FROM `INFORMATION_SCHEMA`.`TABLES` AS `t` "+
				"WHERE `t`.`TABLE_NAME`='%s' AND `t`.`TABLE_SCHEMA` = '%s'", col.Table, col.DB)
			var newRes *QueryResult
			newRes, err = db.Query(sql)
			if err != nil {
				common.Log.Error("(db *Connector) FindColumn Error : ", err)
				return columns, err
			}

			tbCollation := newRes.Rows[0].Str(0)
			if tbCollation != "" {
				col.Character = strings.Split(tbCollation, "_")[0]
				col.Collation = tbCollation
			}
		}

		columns = append(columns, col)
	}

	return columns, err
}

// IsFKey 判断列是否是外键
func (db *Connector) IsFKey(dbName, tbName, column string) bool {
	sql := fmt.Sprintf("SELECT REFERENCED_COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE C "+
		"WHERE REFERENCED_TABLE_SCHEMA <> 'NULL' AND"+
		" TABLE_NAME='%s' AND"+
		" TABLE_SCHEMA='%s' AND"+
		" COLUMN_NAME='%s'", tbName, dbName, column)

	res, err := db.Query(sql)
	if err == nil && len(res.Rows) == 0 {
		return false
	}

	return true
}

// Reference 用于存储关系
type Reference map[string][]ReferenceValue

// ReferenceValue 用于处理表之间的关系
type ReferenceValue struct {
	RefDBName      string // 夫表所属数据库
	RefTable       string // 父表
	DBName         string // 子表所属数据库
	Table          string // 子表
	ConstraintName string // 关系名称
}

// ShowReference 查找所有的外键信息
func (db *Connector) ShowReference(dbName string, tbName ...string) ([]ReferenceValue, error) {
	var referenceValues []ReferenceValue
	sql := `SELECT C.REFERENCED_TABLE_SCHEMA,C.REFERENCED_TABLE_NAME,C.TABLE_SCHEMA,C.TABLE_NAME,C.CONSTRAINT_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE C JOIN INFORMATION_SCHEMA. TABLES T ON T.TABLE_NAME = C.TABLE_NAME
WHERE C.REFERENCED_TABLE_NAME IS NOT NULL`
	sql = sql + fmt.Sprintf(` AND C.TABLE_SCHEMA = "%s"`, dbName)

	if len(tbName) > 0 {
		extra := fmt.Sprintf(` AND C.TABLE_NAME IN ("%s")`, strings.Join(tbName, `","`))
		sql = sql + extra
	}

	// 执行SQL查找外键关联关系
	res, err := db.Query(sql)
	if err != nil {
		return referenceValues, err
	}

	refDb := res.Result.Map("REFERENCED_TABLE_SCHEMA")
	refTb := res.Result.Map("REFERENCED_TABLE_NAME")
	schema := res.Result.Map("TABLE_SCHEMA")
	tb := res.Result.Map("TABLE_NAME")
	cName := res.Result.Map("CONSTRAINT_NAME")

	// 获取值
	for _, row := range res.Rows {
		value := ReferenceValue{
			RefDBName:      row.Str(refDb),
			RefTable:       row.Str(refTb),
			DBName:         row.Str(schema),
			Table:          row.Str(tb),
			ConstraintName: row.Str(cName),
		}
		referenceValues = append(referenceValues, value)
	}

	return referenceValues, err

}
