package spec

import (
	"os"

	"github.com/mitchellh/mapstructure"
	"github.com/pingcap/log"
	"github.com/pingcap/test-infra/caselib/pkg/model/workload"
	"go.uber.org/zap"
	"gopkg.in/yaml.v3"
)

type (
	CaseName string

	CaseSpec struct {
		Kind   string   `yaml:"kind" validate:"required"`
		Name   CaseName `yaml:"name" validate:"required"`
		DBName string   `yaml:"dbName" validate:"required"`
	}

	WorkloadSpec struct {
		Kind   string                 `yaml:"kind" validate:"required"`
		Option map[string]interface{} `yaml:"option"`
	}

	Specification struct {
		CaseSpec     *CaseSpec     `yaml:"case" validate:"required"`
		WorkloadSpec *WorkloadSpec `yaml:"workload" validate:"required"`
	}
)

func (c CaseName) String() string {
	return string(c)
}

func (s *Specification) UnmarshalYAML(unmarshal func(interface{}) error) error {
	type plain Specification
	if err := unmarshal((*plain)(s)); err != nil {
		return err
	}

	return nil
}

func (s *Specification) GetWorkloadOption(command workload.CommandType) workload.Option {
	var (
		spec = s.WorkloadSpec
		op   workload.Option
	)
	switch spec.Kind {
	case "go-ycsb":
		return &workload.QihooOption{
			Command:        command,
			PropertyFile:   "",
			PropertyValues: nil,
			Threads:        0,
			Duration:       0,
			DB:             "",
		}
	case "go-tpc":
		op = &workload.TPCOption{}
	case "sysbench":
		op = &workload.SysbenchOption{}
	case "tiup cluster":
		op = &workload.TiupClusterOption{}
	case "tiup":
		op = &workload.TiupChangefeedOption{}
	case "dmsysbench":
		op = &workload.DmSysbenchOption{}
	default:
		panic("unsupported workload kind, only support `sysbench`, `go-tpc`, `go-ycsb` and `dmsysbench` now")
	}

	decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
		Metadata:   nil,
		Result:     &op,
		DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
	})
	if err != nil {
		log.Error("new decoder for workload option failed", zap.Error(err))
		return nil // just return nil, this will make the caller panic.
	}
	if err := decoder.Decode(spec.Option); err != nil {
		log.Error("decode workload option failed", zap.Error(err))
		return nil
	}
	op.SetCommand(command)
	return op
}

// ReadYamlFile read yaml content from file`
func readYamlFile(file string) ([]byte, error) {
	content, err := os.ReadFile(file)
	if err != nil {
		return nil, err
	}

	return content, nil
}

// ParseYaml read yaml content from `file` and unmarshal it to `out`
func ParseYaml(filePath string, out *Specification) error {
	contents, err := readYamlFile(filePath)
	if err != nil {
		return err
	}

	if err := yaml.Unmarshal(contents, out); err != nil {
		return err
	}

	return nil
}
