// Copyright 2019-2022 Dolthub, 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 benchmark_runner

import (
	"fmt"

	"github.com/google/uuid"
)

type tpccTestParamsImpl struct {
	// NumThreads represents the number of threads running queries concurrently.
	NumThreads int

	// ScaleFactor represents the number of warehouse to test this at scale.
	ScaleFactor int

	// Tables represents the number of tables created per warehouse.
	Tables int

	// TrxLevel represents what transaction level to use
	TrxLevel string

	// ReportCSV determines whether to report output as a csv.
	ReportCSV bool

	// ReportInterval defines how often the tpcc benchmark outputs performance stats.
	ReportInterval int

	// Time represents how long
	Time int
}

var _ TestParams = &tpccTestParamsImpl{}

// NewDefaultTpccParams returns default TpccTestParams.
func NewDefaultTpccParams() *tpccTestParamsImpl {
	return &tpccTestParamsImpl{
		NumThreads:     1, // TODO: When ready, expose as command line argument.
		ScaleFactor:    1,
		Tables:         1,
		TrxLevel:       tpccTransactionLevelRr,
		ReportCSV:      true,
		ReportInterval: 10,
		Time:           800,
	}
}

func (t *tpccTestParamsImpl) GetNumThreads() int {
	return t.NumThreads
}

func (t *tpccTestParamsImpl) GetScaleFactor() int {
	return t.ScaleFactor
}

func (t *tpccTestParamsImpl) GetTables() int {
	return t.Tables
}

func (t *tpccTestParamsImpl) GetTrxLevel() string {
	return t.TrxLevel
}

func (t *tpccTestParamsImpl) GetReportCSV() bool {
	return t.ReportCSV
}

func (t *tpccTestParamsImpl) GetReportInterval() int {
	return t.ReportInterval
}

func (t *tpccTestParamsImpl) GetTime() int {
	return t.Time
}

func (t *tpccTestParamsImpl) ToSlice() []string {
	params := make([]string, 0)
	params = append(params, fmt.Sprintf("%s=%d", tpccThreadsFlag, t.NumThreads))
	params = append(params, fmt.Sprintf("%s=%d", tpccScaleFlag, t.ScaleFactor))
	params = append(params, fmt.Sprintf("%s=%d", tpccTablesFlag, t.Tables))
	params = append(params, fmt.Sprintf("%s=%s", tpccTransactionLevelFlag, t.TrxLevel))
	params = append(params, fmt.Sprintf("%s=%t", tpccReportCsv, t.ReportCSV))
	params = append(params, fmt.Sprintf("%s=%d", tpccReportIntervalFlag, t.ReportInterval))
	params = append(params, fmt.Sprintf("%s=%d", tpccTimeFlag, t.Time))
	return params
}

// tpccTestImpl encapsulates an End to End prepare, run, cleanup test case.
type tpccTestImpl struct {
	// Id represents a unique test id
	Id string

	// Name represents the name of the test case
	Name string

	// Params are associated parameters this test runs with
	Params TpccTestParams
}

var _ Test = &tpccTestImpl{}

// NewTpccTest instantiates and returns a TPCC test.
func NewTpccTest(name string, params TpccTestParams) *tpccTestImpl {
	return &tpccTestImpl{
		Id:     uuid.New().String(),
		Name:   name,
		Params: params,
	}
}

func (t *tpccTestImpl) doltArgs(serverConfig ServerConfig) []string {
	args := make([]string, 0)
	args = append(args, defaultTpccParams...)
	args = append(args, fmt.Sprintf("%s=%s", tpccMysqlHostFlag, serverConfig.GetHost()))
	port := serverConfig.GetPort()
	if port > 0 {
		args = append(args, fmt.Sprintf("%s=%d", tpccMysqlPortFlag, serverConfig.GetPort()))
	}
	args = append(args, fmt.Sprintf("%s=%d", tpccMysqlPortFlag, serverConfig.GetPort()))
	args = append(args, fmt.Sprintf("%s=%s", tpccMysqlUserFlag, defaultMysqlUser))
	args = append(args, fmt.Sprintf("%s=%d", tpccTimeFlag, t.Params.GetTime()))
	args = append(args, fmt.Sprintf("%s=%d", tpccThreadsFlag, t.Params.GetNumThreads()))
	args = append(args, fmt.Sprintf("%s=%d", tpccReportIntervalFlag, t.Params.GetReportInterval()))
	args = append(args, fmt.Sprintf("%s=%d", tpccTablesFlag, t.Params.GetTables()))
	args = append(args, fmt.Sprintf("%s=%d", tpccScaleFlag, t.Params.GetScaleFactor()))
	args = append(args, fmt.Sprintf("%s=%s", tpccTransactionLevelFlag, t.Params.GetTrxLevel()))
	return args
}

func (t *tpccTestImpl) mysqlArgs(serverConfig ServerConfig) []string {
	args := make([]string, 0)
	args = append(args, defaultTpccParams...)
	host := serverConfig.GetHost()
	port := serverConfig.GetPort()
	args = append(args, fmt.Sprintf("%s=%s", tpccMysqlHostFlag, host))
	if host == defaultHost {
		args = append(args, fmt.Sprintf("%s=%s", tpccMysqlUserFlag, tpccMysqlUsername))
		args = append(args, fmt.Sprintf("%s=%s", tpccMysqlPasswordFlag, tpccPassLocal))
	} else {
		args = append(args, fmt.Sprintf("%s=%s", tpccMysqlUserFlag, defaultMysqlUser))
	}
	if port > 0 {
		args = append(args, fmt.Sprintf("%s=%d", tpccMysqlPortFlag, serverConfig.GetPort()))
	}
	args = append(args, fmt.Sprintf("%s=%d", tpccTimeFlag, t.Params.GetTime()))
	args = append(args, fmt.Sprintf("%s=%d", tpccThreadsFlag, t.Params.GetNumThreads()))
	args = append(args, fmt.Sprintf("%s=%d", tpccReportIntervalFlag, t.Params.GetReportInterval()))
	args = append(args, fmt.Sprintf("%s=%d", tpccTablesFlag, t.Params.GetTables()))
	args = append(args, fmt.Sprintf("%s=%d", tpccScaleFlag, t.Params.GetScaleFactor()))
	args = append(args, fmt.Sprintf("%s=%s", tpccTransactionLevelFlag, t.Params.GetTrxLevel()))
	return args
}

func (t *tpccTestImpl) doltgresArgs(serverConfig ServerConfig) []string {
	args := make([]string, 0)
	args = append(args, defaultTpccParams...)
	args = append(args, fmt.Sprintf("%s=%d", tpccTimeFlag, t.Params.GetTime()))
	args = append(args, fmt.Sprintf("%s=%d", tpccThreadsFlag, t.Params.GetNumThreads()))
	args = append(args, fmt.Sprintf("%s=%d", tpccReportIntervalFlag, t.Params.GetReportInterval()))
	args = append(args, fmt.Sprintf("%s=%d", tpccTablesFlag, t.Params.GetTables()))
	args = append(args, fmt.Sprintf("%s=%d", tpccScaleFlag, t.Params.GetScaleFactor()))
	args = append(args, fmt.Sprintf("%s=%s", tpccTransactionLevelFlag, t.Params.GetTrxLevel()))
	return args
}

func (t *tpccTestImpl) postgresArgs(serverConfig ServerConfig) []string {
	args := make([]string, 0)
	args = append(args, defaultTpccParams...)
	args = append(args, fmt.Sprintf("%s=%d", tpccTimeFlag, t.Params.GetTime()))
	args = append(args, fmt.Sprintf("%s=%d", tpccThreadsFlag, t.Params.GetNumThreads()))
	args = append(args, fmt.Sprintf("%s=%d", tpccReportIntervalFlag, t.Params.GetReportInterval()))
	args = append(args, fmt.Sprintf("%s=%d", tpccTablesFlag, t.Params.GetTables()))
	args = append(args, fmt.Sprintf("%s=%d", tpccScaleFlag, t.Params.GetScaleFactor()))
	args = append(args, fmt.Sprintf("%s=%s", tpccTransactionLevelFlag, t.Params.GetTrxLevel()))
	return args
}

// getArgs returns a test's args for all TPCC steps
func (t *tpccTestImpl) getArgs(serverConfig ServerConfig) []string {
	st := serverConfig.GetServerType()
	switch st {
	case Dolt:
		return t.doltArgs(serverConfig)
	case Doltgres:
		return t.doltgresArgs(serverConfig)
	case Postgres:
		return t.postgresArgs(serverConfig)
	case MySql:
		return t.mysqlArgs(serverConfig)
	default:
		panic(fmt.Sprintf("unexpected server type: %s", st))
	}
}

func (t *tpccTestImpl) GetId() string {
	return t.Id
}

func (t *tpccTestImpl) GetName() string {
	return t.Name
}

func (t *tpccTestImpl) GetParamsToSlice() []string {
	return t.Params.ToSlice()
}

func (t *tpccTestImpl) GetPrepareArgs(serverConfg ServerConfig) []string {
	args := make([]string, 0)
	serverArgs := t.getArgs(serverConfg)
	args = append(args, serverArgs...)
	args = append(args, sysbenchPrepareCommand)
	return args
}

func (t *tpccTestImpl) GetRunArgs(serverConfg ServerConfig) []string {
	args := make([]string, 0)
	serverArgs := t.getArgs(serverConfg)
	args = append(args, serverArgs...)
	args = append(args, sysbenchRunCommand)
	return args
}

func (t *tpccTestImpl) GetCleanupArgs(serverConfg ServerConfig) []string {
	args := make([]string, 0)
	serverArgs := t.getArgs(serverConfg)
	args = append(args, serverArgs...)
	args = append(args, sysbenchCleanupCommand)
	return args
}
