package dbctrl

import (
	"database/sql"
	"errors"
	"fmt"
	"strings"
	"validate/util"
)

const MySQLDatabase = "mysql"

var SystemDatabaseList = [...]string{"INFORMATION_SCHEMA", "METRICS_SCHEMA", "PERFORMANCE_SCHEMA"}
var SystemTableInMySQLList = [...]string{"GLOBAL_VARIABLES",
	"advisory_locks",
	"analyze_jobs",
	"analyze_options",
	"bind_info",
	"capture_plan_baselines_blacklist",
	"column_stats_usage",
	"columns_priv",
	"db",
	"default_roles",
	"dist_framework_meta",
	"expr_pushdown_blacklist",
	"gc_delete_range",
	"gc_delete_range_done",
	"global_grants",
	"global_priv",
	"help_topic",
	"load_data_jobs",
	"opt_rule_blacklist",
	"password_history",
	"plan_replayer_status",
	"plan_replayer_task",
	"role_edges",
	"schema_index_usage",
	"stats_buckets",
	"stats_extended",
	"stats_feedback",
	"stats_fm_sketch",
	"stats_histograms",
	"stats_history",
	"stats_meta",
	"stats_meta_history",
	"stats_table_locked",
	"stats_top_n",
	"table_cache_meta",
	"tables_priv",
	"tidb",
	"tidb_background_subtask",
	"tidb_background_subtask_history",
	"tidb_ddl_history",
	"tidb_ddl_job",
	"tidb_ddl_reorg",
	"tidb_global_task",
	"tidb_global_task_history",
	"tidb_import_jobs",
	"tidb_mdl_info",
	"tidb_mdl_view",
	"tidb_runaway_queries",
	"tidb_runaway_watch",
	"tidb_runaway_watch_done",
	"tidb_timers",
	"tidb_ttl_job_history",
	"tidb_ttl_table_status",
	"tidb_ttl_task",
	"user"}

const ShowSchemaListStmt = "select * from information_schema.SCHEMATA order by schema_name;"

type DatabaseInfo struct {
	CATALOG_NAME               string
	SCHEMA_NAME                string
	DEFAULT_CHARACTER_SET_NAME string
	DEFAULT_COLLATION_NAME     string
	SQL_PATH                   string
	TIDB_PLACEMENT_POLICY_NAME string

	TableList []*TableInformationSchema
}

func GetDatabaseByStmt(tx *sql.Tx, stmt string) ([]*DatabaseInfo, error) {
	if tx == nil || len(stmt) < 1 {
		return nil, errors.New("empty transaction object")
	}

	var dbs []*DatabaseInfo
	rows, err := tx.Query(stmt)
	if err != nil {
		return nil, err
	}

	for rows.Next() {
		var db = &DatabaseInfo{}
		var sqlPath sql.NullString
		var policy sql.NullString
		if err = rows.Scan(
			&db.CATALOG_NAME,
			&db.SCHEMA_NAME,
			&db.DEFAULT_CHARACTER_SET_NAME,
			&db.DEFAULT_COLLATION_NAME,
			&sqlPath,
			&policy,
		); err != nil {
			util.Logger.Fatalf(err.Error())
		}

		if sqlPath.Valid {
			db.SQL_PATH = sqlPath.String
		} else {
			db.SQL_PATH = ""
		}
		if policy.Valid {
			db.TIDB_PLACEMENT_POLICY_NAME = policy.String
		} else {
			db.TIDB_PLACEMENT_POLICY_NAME = ""
		}

		dbs = append(dbs, db)
	}
	if err = rows.Err(); err != nil {
		return nil, err
	}

	return dbs, nil
}

func GetAllDatabase(tx *sql.Tx) ([]*DatabaseInfo, error) {
	return GetDatabaseByStmt(tx, ShowSchemaListStmt)
}

func (db *DatabaseInfo) PrintDatabase() string {
	dbString := fmt.Sprintf("[%s], with [%d] tables in it\n", db.SCHEMA_NAME, len(db.TableList))

	if len(db.TableList) <= 0 {
		return dbString
	}

	dbString += PrintTableBriefHeader()
	for _, tbl := range db.TableList {
		dbString += tbl.PrintTableBrief()
	}

	dbString += PrintTableBriefTail()

	return dbString
}

func (db *DatabaseInfo) IsSystemDatabase() bool {
	for _, name := range SystemDatabaseList {
		if 0 == strings.Compare(strings.TrimSpace(db.SCHEMA_NAME), name) {
			return true
		}
	}

	return false
}

func (db *DatabaseInfo) ExportDatabaseDetail(tx *sql.Tx) error {
	if tx == nil {
		return errors.New("empty transaction object")
	}

	if db.IsSystemDatabase() {
		return errors.New("system database not supported")
	}

	tblNameList, err := GetTableNameList(tx)
	if err != nil {
		rollbackErr := tx.Rollback()
		if rollbackErr != nil {
			util.Logger.Fatalf("failed to ROLLBACK transaction, " + rollbackErr.Error())
		}
		return err
	}

	var tableList []*TableInformationSchema
	for _, tableName := range tblNameList {
		if IsSystemTableInMySQL(db.SCHEMA_NAME, tableName) {
			continue
		}

		table, err := GetTableDetail(db.SCHEMA_NAME, tableName, tx)
		if err != nil {
			rollbackErr := tx.Rollback()
			if rollbackErr != nil {
				util.Logger.Fatalf("failed to ROLLBACK transaction, " + rollbackErr.Error())
			}
			return err
		}
		tableList = append(tableList, table)
	}

	db.TableList = tableList

	return nil
}
