package flow

import (
	"flag"
	"strconv"
	"validate/dbctrl"
	"validate/transactional_operation"
	"validate/util"

	"github.com/pingcap/errors"
)

func EstimatePrimaryCommand() *EstimatePrimaryCommandCtx {
	ca := &EstimatePrimaryCommandCtx{
		ClusterMigrationSubCommandCtx{
			fs: flag.NewFlagSet(EstimatePrimaryClusterCommand, flag.ContinueOnError),
		},
	}
	ca.fs.StringVar(&ca.CommandTarget, "target", "all", "Sub-actions of the command.")

	ca.fs.StringVar(&ca.PrimaryAddress.Host, PrefixPrimaryClusterAddr+"host", "127.0.0.1", "Host address of the primary TiDB Cluster, 'localhost' as default.")
	ca.fs.IntVar(&ca.PrimaryAddress.Port, PrefixPrimaryClusterAddr+"port", 4000, "Port of the primary TiDB Cluster, 4000 as default.")
	ca.fs.StringVar(&ca.PrimaryAddress.Database, PrefixPrimaryClusterAddr+"dbname", "mysql", "Database name of the primary TiDB Cluster, 'mysql' as default.")
	ca.fs.StringVar(&ca.PrimaryAddress.Account, PrefixPrimaryClusterAddr+"name", "root", "Database account of the primary TiDB Cluster, 'root' as default.")
	ca.fs.StringVar(&ca.PrimaryAddress.Password, PrefixPrimaryClusterAddr+"password", "", "Host address of the primary TiDB Cluster, '' as default.")

	return ca
}

type EstimatePrimaryCommandCtx struct {
	ClusterMigrationSubCommandCtx
}

func (epc *EstimatePrimaryCommandCtx) Name() string {
	return epc.fs.Name()
}

func (epc *EstimatePrimaryCommandCtx) Init(args []string) error {
	return epc.fs.Parse(args)
}

func (epc *EstimatePrimaryCommandCtx) Execute() error {
	return epc.EstimatePrimaryMain()
}

func (epc *EstimatePrimaryCommandCtx) estimateDBAccounts() error {
	util.Logger.Printf("-- Estimate DB accounts in Primary TiDB Cluster.\n")

	dbAccounts, err := transactional_operation.ExportAllDBUserDefinition(epc.PrimaryDBConn)
	if err != nil {
		return err
	}

	util.Logger.Printf("-- DB Account List, total [" + strconv.Itoa(len(dbAccounts)) + "].\n")
	err = dbctrl.PrintDBAccountList(dbAccounts)
	if err != nil {
		return err
	}
	util.Logger.Printf("----------------------------------------\n")

	return nil
}

func (epc *EstimatePrimaryCommandCtx) estimateSchemas() error {
	util.Logger.Printf("-- Estimate Schemas in Primary TiDB Cluster.\n")

	dbList, err := transactional_operation.ExportDatabaseList(epc.PrimaryDBConn)
	if err != nil {
		return err
	}

	var dbInfoList []*dbctrl.DatabaseInfo
	for _, db := range dbList {
		dbInfo, err := transactional_operation.ExportDatabase(db, epc.PrimaryAddress)
		if err != nil {
			errors.Wrap(err, "failed to export database detail from primary TiDB Cluster")
		}
		dbInfoList = append(dbInfoList, dbInfo)
	}

	var dbInfoStr = ""
	util.Logger.Printf("-- Schema list, total [" + strconv.Itoa(len(dbInfoList)) + "].\n")
	for idx, dbInfo := range dbInfoList {
		dbInfoStr += "---- " + strconv.Itoa(idx+1) + ". " + dbInfo.PrintDatabase()
	}

	util.Logger.Printf("----------------------------------------\n")
	return nil
}

func (epc *EstimatePrimaryCommandCtx) estimateGlobalVariables() error {
	util.Logger.Printf("-- Estimate Global Variables in Primary TiDB Cluster.\n")

	variables, err := transactional_operation.GetGlobalVariableList(epc.PrimaryDBConn)
	if err != nil {
		return err
	}

	util.Logger.Printf("-- Global Variable list, total [" + strconv.Itoa(len(variables)) + "].\n")
	util.Logger.Printf("----------------------------------------\n")

	return nil
}

func (epc *EstimatePrimaryCommandCtx) estimateConfigurations() error {
	util.Logger.Printf("-- Estimate Configurations in Primary TiDB Cluster.\n")

	configList, err := transactional_operation.GetConfigurationList(epc.PrimaryDBConn)
	if err != nil {
		return err
	}

	util.Logger.Printf("-- All configuration list, total [" + strconv.Itoa(len(configList)) + "].\n")
	util.Logger.Printf("----------------------------------------\n")
	return nil
}

func (epc *EstimatePrimaryCommandCtx) estimatePlacementRules() error {
	util.Logger.Printf("-- Estimate Placement Rules in Primary TiDB Cluster.\n")

	globalVariableList, err := transactional_operation.GetPlacementPolicyList(epc.PrimaryDBConn)
	if err != nil {
		return err
	}

	util.Logger.Printf("-- All Placement Rule list, total [" + strconv.Itoa(len(globalVariableList)) + "].\n")
	util.Logger.Printf("----------------------------------------\n")

	return nil
}

func (epc *EstimatePrimaryCommandCtx) estimateSQLBindings() error {
	util.Logger.Printf("-- Estimate SQL Binding in Primary TiDB Cluster.\n")

	sqlBindingList, err := transactional_operation.GetSQLBindingList(epc.PrimaryDBConn)
	if err != nil {
		return err
	}

	util.Logger.Printf("-- All SQL Binding list, total [" + strconv.Itoa(len(sqlBindingList)) + "].\n")
	util.Logger.Printf("----------------------------------------\n")

	return nil
}

func (epc *EstimatePrimaryCommandCtx) estimateAll() (err error) {
	err = epc.estimateDBAccounts()
	if err != nil {
		return err
	}
	err = epc.estimateSchemas()
	if err != nil {
		return err
	}
	err = epc.estimateGlobalVariables()
	if err != nil {
		return err
	}
	err = epc.estimateConfigurations()
	if err != nil {
		return err
	}
	err = epc.estimatePlacementRules()
	if err != nil {
		return err
	}
	err = epc.estimateSQLBindings()
	if err != nil {
		return err
	}

	return nil
}

func (epc *EstimatePrimaryCommandCtx) EstimatePrimaryMain() (err error) {
	epc.PrimaryDBConnPool, err = dbctrl.NewDBConnInfoByAdd(&epc.PrimaryAddress)
	if err != nil {
		return err
	}

	util.Logger.Printf("Estimate the Primary TiDB Cluster.\n")

	epc.PrimaryDBConn, err = epc.PrimaryDBConnPool.Connect()
	if err != nil {
		return err
	}
	defer epc.PrimaryDBConn.Close()

	switch epc.CommandTarget {
	case CommandTargetAll:
		err = epc.estimateAll()
	case CommandTargetDBAccount:
		err = epc.estimateDBAccounts()
	case CommandTargetSchema:
		err = epc.estimateSchemas()
	case CommandTargetGlobalVariable:
		err = epc.estimateGlobalVariables()
	case CommandTargetConfiguration:
		err = epc.estimateConfigurations()
	case CommandTargetPlacementRule:
		err = epc.estimatePlacementRules()
	case CommandTargetSQLBinding:
		err = epc.estimateSQLBindings()
	default:
		err = errors.New("unsupported action during 'estimate primary'")
	}

	if err != nil {
		return err
	}

	util.Logger.Printf("Finish the estimation of the Primary TiDB Cluster.\n")
	return nil
}
