package large

import (
	"database/sql"
	"fmt"
	"os"
	"strconv"
	"time"

	"github.com/pingcap/errors"
)

type Config struct {
	Conn    ConnConfig
	Prepare PrepareConfig
	Run     RunConfig
	Clean   CleanTask
}

func DefaultConfig() Config {
	cfg := Config{
		Conn:    DefaultConnConfig(),
		Prepare: DefaultPrepareConfig(),
		Run:     DefaultRunConfig(),
		Clean:   DefaultCleanTask(),
	}
	cfg.Conn.waitDBReady(5 * time.Minute)
	return cfg
}

// Cmd is provided for the commandline flag.
var Cmd = DefaultConfig()

const (
	EnvConnHost   = "LARGE_TXN_HOST"
	EnvConnPort   = "LARGE_TXN_PORT"
	EnvConnUser   = "LARGE_TXN_USER"
	EnvConnDB     = "LARGE_TXN_DB"
	EnvConnStatus = "LARGE_TXN_STATUS"
)

type ConnConfig struct {
	User string
	Host string
	Port int
	DB   string
	// The TiDB status port
	Status int
}

func (conf *ConnConfig) getDSN() string {
	return fmt.Sprintf("%s@tcp(%s:%d)/%s?allowAllFiles=true", conf.User, conf.Host, conf.Port, conf.DB)
}

func (conf *ConnConfig) waitDBReady(timeout time.Duration) {
	dsn := conf.getDSN()
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		fmt.Println("cannot open dsn: " + dsn)
		panic(err)
	}
	defer db.Close()

	// ping asynchronously because `db.Ping()` may block for a long time.
	ping := func() chan bool {
		done := make(chan bool, 1)
		go func() { defer close(done); done <- db.Ping() == nil }()
		return done
	}

	start := time.Now()
	tick := time.NewTicker(5 * time.Second)
	defer tick.Stop()
	for {
		if time.Since(start) > timeout {
			panic("wait db ready timeout")
		}
		select {
		case <-tick.C:
			fmt.Println("db(" + dsn + ") is not ready")
		case ok := <-ping():
			if ok {
				return
			}
		}
	}
}

func getEnv(name string, optDefault ...string) string {
	if val := os.Getenv(name); len(val) > 0 {
		return val
	}
	if len(optDefault) > 0 {
		return optDefault[0]
	}
	return ""
}

func DefaultConnConfig() ConnConfig {
	toInt := func(s string) int {
		i, err := strconv.Atoi(s)
		if err != nil {
			panic(errors.Annotate(err, "invalid port"))
		}
		return i
	}
	return ConnConfig{
		User:   getEnv(EnvConnUser, "root"),
		Host:   getEnv(EnvConnHost, "172.16.4.61"),
		DB:     getEnv(EnvConnDB, "test"),
		Port:   toInt(getEnv(EnvConnPort, "14000")),
		Status: toInt(getEnv(EnvConnStatus, "11080")),
	}
}

type PrepareConfig struct {
	WorkerCount int
	Task        PrepareTask
}

func DefaultPrepareConfig() PrepareConfig {
	return PrepareConfig{
		WorkerCount: 4,
		Task:        DefaultPrepareTask(),
	}
}

var DataSize3G = PrepareConfig{
	WorkerCount: 8,
	Task: BasePrepareTask{
		InsertCount:    3 << 30 / 512 / 8 / 50,
		RowsEachInsert: 50,
	},
}

var DataSize1G = PrepareConfig{
	WorkerCount: 4,
	Task: BasePrepareTask{
		InsertCount:    1 << 30 / 512 / 4 / 50,
		RowsEachInsert: 50,
	},
}

var DataSize10G = PrepareConfig{
	WorkerCount: 16,
	Task: BasePrepareTask{
		InsertCount:    10 << 30 / 512 / 16 / 50,
		RowsEachInsert: 50,
	},
}

type RunConfig struct {
	Large LargeTxnConfig
	Small SmallTxnConfig
	Chaos Chaos
}

func DefaultRunConfig() RunConfig {
	return RunConfig{
		Large: DefaultLargeTxnConfig(),
		Small: DefaultSmallTxnConfig(),
	}
}

type LargeTxnConfig struct {
	TxnMode
	Task LargeTxner
}

func DefaultLargeTxnConfig() LargeTxnConfig {
	return LargeTxnConfig{
		TxnMode: Optimistic,
		Task:    LargeUpdate{},
	}
}

type SmallTxnConfig struct {
	TxnMode
	WorkerCount int
	Task        SmallTxner
}

func DefaultSmallTxnConfig() SmallTxnConfig {
	return SmallTxnConfig{
		WorkerCount: 4,
		TxnMode:     Optimistic,
		Task:        SelectRandomPoints{},
	}
}
