package mock

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"github.com/tidbcloud/pov-service/internal/common/constant"
	"github.com/tidbcloud/pov-service/internal/common/params"
	"github.com/tidbcloud/pov-service/internal/config"
	"github.com/tidbcloud/pov-service/internal/model"
	"github.com/tidbcloud/pov-service/internal/pkg/log"
	"github.com/tidbcloud/pov-service/internal/repo"
	"github.com/tidbcloud/pov-service/internal/service"
)

var isProd = config.IsProdEnv()

type WorkloadAdaptor struct {
	service.Workload

	workloadTestRepo repo.WorkloadTest
}

func NewAdaptor(
	w service.Workload,
	workloadTestRepo repo.WorkloadTest,
) service.Workload {
	return WorkloadAdaptor{
		Workload:         w,
		workloadTestRepo: workloadTestRepo,
	}
}

func (s WorkloadAdaptor) ListWorkloads(ctx context.Context, param params.ListWorkloads) ([]model.Workload, error) {
	rawRet, err := s.Workload.ListWorkloads(ctx, param)
	if err != nil {
		return nil, err
	}
	rawRet, err = s.filterWorkloads(rawRet, param)
	if err != nil {
		return nil, err
	}

	// Deep copy rawRet to ret to avoid modifying the original data directly.
	// This is important because subsequent operations will filter fields,
	// and we don't want to affect the original rawRet.
	ret := make([]model.Workload, len(rawRet))
	copy(ret, rawRet)

	// Set DataSource
	for i := range ret { // Iterate over `ret` instead of `rawRet`
		s.configDataSourceParam(&ret[i])
	}

	for i := range ret {
		s.filterVisibleFields(&ret[i])
	}

	// setup lower/upper limit
	for i := range ret {
		s.configLimit(&ret[i])
	}

	// set default cluster RU
	for i := range ret {
		s.configClusterRU(&ret[i])
	}

	return ret, nil
}

func (s WorkloadAdaptor) GetWorkload(ctx context.Context, id int64) (model.Workload, error) {
	ret, err := s.Workload.GetWorkload(ctx, id)
	if err != nil {
		return model.Workload{}, err
	}

	s.configDataSourceParam(&ret)

	s.filterVisibleFields(&ret)

	// setup lower/upper limit
	s.configLimit(&ret)

	// set default cluster RU
	s.configClusterRU(&ret)

	return ret, nil
}

func (w WorkloadAdaptor) RunWorkloadTest(
	ctx context.Context,
	param params.RunWorkload,
	startedBy string,
) (model.WorkloadTest, error) {
	newParams := param.GetParams()
	if newParams == nil {
		newParams = model.WorkloadParams(make([]model.WorkloadParam, 0))
	}

	// BR is not supported in production ng. We can only use sql generate to prepare data
	if shouldUseImport(param) {
		for i := range newParams {
			if newParams[i].Name == string(constant.DataSource) {
				brParams := workloadProdPrepareParams(param.WorkloadID, newParams[i].Value)
				for k, v := range brParams {
					newParams.Set(k, v)
				}
				break
			}
		}
	} else {
		// Set BRStorageURL
		for i := range newParams {
			if newParams[i].Name == string(constant.DataSource) {
				brSource := workloadBRValue(param.WorkloadID, newParams[i].Value)
				newParams.Set(string(constant.BrStorageURL), brSource)
				break
			}
		}
	}

	// Set BR-Specific Param, for example, tpc warehouses and sysbench tables/size
	for i := range newParams {
		if newParams[i].Name == string(constant.DataSource) {
			brParams := workloadBRParams(param.WorkloadID, newParams[i].Value)
			for k, v := range brParams {
				newParams.Set(k, v)
			}
			break
		}
	}

	param.Params = &newParams

	// Set Client Resource
	// For demo, hardcode to use 7 cpu and 13Gi memory
	newParams.Set(string(constant.ClientResource), "{ \"cpu\": \"7\", \"memory\": \"13Gi\" }")

	return w.Workload.RunWorkloadTest(ctx, param, startedBy)
}

// @MimeLyc: Because the workload templates are maintained in tcms, and it's a temporary solution,
//
//	so I preferred to implement all filter logic in the mock adaptor layer.
//	And once we build the template into the pov service(in db or file mode), move filter into corresponding layers.
func (s WorkloadAdaptor) filterWorkloads(
	workloads []model.Workload,
	param params.ListWorkloads,
) ([]model.Workload, error) {
	filterPrepare := func(wl []model.Workload) []model.Workload {
		ret := make([]model.Workload, 0)
		for _, wl := range wl {
			if isWorkloadSupportSqlGenerate(wl.ID) || isWorkloadSupportImport(wl.ID) {
				ret = append(ret, wl)
			}
		}
		return ret
	}
	filterType := func(wl []model.Workload, typ model.WorkloadType) []model.Workload {
		if typ == "" {
			return wl
		}
		ret := make([]model.Workload, 0)
		for _, wl := range wl {
			if strings.EqualFold(string(wl.Type), string(param.Type)) {
				ret = append(ret, wl)
			}
		}
		return ret
	}
	filterIndustry := func(wl []model.Workload, ind string) []model.Workload {
		if ind == "" {
			return wl
		}
		ret := make([]model.Workload, 0)
		for _, wl := range wl {
			for _, tag := range wl.Tags {
				if strings.Contains(strings.ToLower(tag), strings.ToLower(ind)) {
					ret = append(ret, wl)
				}
			}
		}
		return ret
	}
	filterKeyword := func(wl []model.Workload, keyword string) []model.Workload {
		if keyword == "" {
			return wl
		}
		ret := make([]model.Workload, 0)
		for _, wl := range wl {
			if strings.Contains(strings.ToLower(wl.Name), strings.ToLower(keyword)) {
				ret = append(ret, wl)
			}
		}
		return ret
	}

	if isProd {
		workloads = filterPrepare(workloads)
	}
	if param.Type != "" {
		workloads = filterType(workloads, param.Type)
	}
	if param.Industry != "" {
		workloads = filterIndustry(workloads, param.Industry)
	}
	if param.Keyword != "" {
		workloads = filterKeyword(workloads, param.Keyword)
	}

	return workloads, nil
}

func (s WorkloadAdaptor) filterWorkloadTests(
	tests []model.WorkloadTest,
) ([]model.WorkloadTest, error) {
	filterSqlGen := func(wl []model.WorkloadTest) []model.WorkloadTest {
		ret := make([]model.WorkloadTest, 0)
		for _, wl := range wl {
			if isWorkloadSupportSqlGenerate(wl.WorkloadID) {
				ret = append(ret, wl)
			}
		}
		return ret
	}

	if isProd {
		return filterSqlGen(tests), nil
	}

	return tests, nil
}

func (s WorkloadAdaptor) configDataSourceParam(
	wl *model.Workload,
) {
	for i := range wl.ParamsTemplate {
		if wl.ParamsTemplate[i].Name == string(constant.DataSource) {
			labels := WorkloadBRLabels(wl.ID)
			// if there is no options, remove this field
			if len(labels) == 0 {
				wl.ParamsTemplate = append(wl.ParamsTemplate[:i], wl.ParamsTemplate[i+1:]...)
				break
			}
			options := make([]any, len(labels))
			for j := range labels {
				options[j] = labels[j]
			}
			wl.ParamsTemplate[i].Options = options

			// Set default value to the first option
			wl.ParamsTemplate[i].Value = labels[0]
			break
		}
	}
}

func (s WorkloadAdaptor) filterVisibleFields(
	wl *model.Workload,
) {
	visibleFields := make([]model.WorkloadField, 0, len(wl.ParamsTemplate))
	for j, field := range wl.ParamsTemplate {
		if IsFeVisible(wl.ParamsTemplate[j]) {
			visibleFields = append(visibleFields, field)
		}
	}
	wl.ParamsTemplate = visibleFields
}

func (s WorkloadAdaptor) configLimit(
	wl *model.Workload,
) {
	for j := range wl.ParamsTemplate {
		switch wl.ParamsTemplate[j].Name {
		case string(constant.Duration):
			wl.ParamsTemplate[j].LowerLimit = "1"
			wl.ParamsTemplate[j].UpperLimit = "500"
		case string(constant.MaxConnectionNumber):
			def := wl.ParamsTemplate[j].Value.(string)
			defVal, err := strconv.Atoi(def)
			if err != nil {
				log.Log.Errorf(err, "failed to parse default value %s", def)
				continue
			}
			wl.ParamsTemplate[j].LowerLimit = "1"
			wl.ParamsTemplate[j].UpperLimit = fmt.Sprintf("%d", defVal*5)
		}
	}
}

func (s WorkloadAdaptor) configClusterRU(
	wl *model.Workload,
) {

	wl.ParamsTemplate = append(wl.ParamsTemplate, model.WorkloadField{
		Name:       "min_ru",
		Value:      "10000",
		Type:       "String",
		Required:   true,
		LowerLimit: "5000",
		UpperLimit: "50000",
	}, model.WorkloadField{
		Name:       "max_ru",
		Value:      "10000",
		Type:       "String",
		Required:   true,
		LowerLimit: "5000",
		UpperLimit: "50000",
	})
}

// the visibility between FE and BE should be different.
// FE can only see user configured value, but BE can see all params
// We define value of this field according to whether the @Value field is required NOW.
func IsFeVisible(w model.WorkloadField) bool {
	return w.Required
}

func getUpdateWorkloadTestParams(
	test model.WorkloadTest) (toUpdate params.UpdateWorkloadTest, err error) {
	for _, param := range test.Params {
		if param.Name == string(constant.MaxConnectionNumber) {
			toUpdate.MaxConn, err = strconv.Atoi(param.Value)
		}
		if param.Name == string(constant.MaxRU) {
			toUpdate.MaxRU, err = strconv.ParseInt(param.Value, 10, 64)
		}
		if param.Name == string(constant.MinRU) {
			toUpdate.MinRU, err = strconv.ParseInt(param.Value, 10, 64)
		}
	}

	return toUpdate, err
}

func shouldUseImport(
	params params.RunWorkload,
) bool {
	typ, err := params.Params.GetClusterType()
	return isProd || err != nil || typ == model.ClusterTypeTiDBCloudEssential
}
