package types

import (
	"time"

	"github.com/ngaut/log"
	"github.com/pingcap/schrodinger/util"
)

// ExperimentState is experiment state
type ExperimentState string

// ExperimentState
const (
	ExperimentStateNormal   ExperimentState = "NORMAL"
	ExperimentStateAbnormal                 = "ABNORMAL"
)

// ExperimentStage is experiment stage
type ExperimentStage string

// ExperimentStage
const (
	ExperimentStagePending   ExperimentStage = "PENDING"
	ExperimentStagePreparing                 = "PREPARING"
	ExperimentStageRunning                   = "RUNNING"
	ExperimentStageFinishing                 = "FINISHING"
	ExperimentStageFinished                  = "FINISHED"
	ExperimentStageOmit                      = "OMIT"
)

// ExperimentConfig is experiment config, it contains cluster config and case config
// It will be used to create Experiment struct
type ExperimentConfig struct {
	// if `WithoutCat` is true, the experiments of the box just run the case.
	WithoutCat bool
	// experiment use Cat to create/select cluster
	Cat *Cat
	// experiment use misc to do tasks
	Misc *MiscConfig
	// experiment use Test to create test
	Tests *TestsConfig
}

// ExperimentBrief is experiment brief
type ExperimentBrief struct {
	ID   int64  `json:"id"`
	Name string `json:"name"`

	BoxID    int64 `json:"box"`
	BoxIndex int64 `json:"box_index"`

	Ctime string `json:"create_time"`
	Utime string `json:"update_time"`

	State     ExperimentState `json:"status"`
	Stage     ExperimentStage `json:"stage"`
	Destroyed bool            `json:"destroyed"`

	RunVersion RunVersion        `json:"run_version"`
	Config     TiDBClusterConfig `json:"config"`
	DestroyCat bool              `json:"destroy_cat"`

	Author     string `json:"author"`
	Annotation string `json:"annotation"`
}

// ExperimentProcess is an experiment on one cat instance
type ExperimentProcess struct {
	*Meta
	ID        int64  `json:"id"`
	Name      string `json:"name"`
	Namespace string `json:"namespace"`

	BoxID   int64  `json:"box"`
	BoxName string `json:"box_name"`
	Creator string `json:"creator"`

	// experiment index in box
	BoxIndex int64 `json:"box_index"`

	Ctime     string `json:"create_time"`
	Utime     string `json:"update_time"`
	Ftime     string `json:"fail_time"`
	StartTime string `json:"start_time"`
	EndTime   string `json:"end_time"`

	State         ExperimentState `json:"status"`
	Stage         ExperimentStage `json:"stage"`
	Destroyed     bool            `json:"destroyed"`
	StatusMessage string          `json:"status_message"`

	Author     string `json:"author"`
	Annotation string `json:"annotation"`

	Config     *ExperimentConfig
	RunVersion RunVersion

	// CatCount is a counter for cat how many times it replaced
	CatCount int64                   `json:"cat_count"`
	Cat      *CatProcess             `json:"cat"`
	Tests    map[string]*TestProcess `json:"tests"`

	Timeout util.Duration     `json:"timeout"`
	Report  *ExperimentReport `json:"report"`
}

// Clone clones experiment process
func (experiment *ExperimentProcess) Clone() *ExperimentProcess {
	experimentClone := &ExperimentProcess{}
	if err := util.Clone(experimentClone, experiment); err != nil {
		log.Errorf("clone experiment %d error %+v", experiment.ID, err)
		return nil
	}
	return experimentClone
}

// CreateRedoProcess create restart experiment process
func (experiment *ExperimentProcess) CreateRedoProcess() *ExperimentProcess {
	id := util.UniqID()
	experimentClone := &ExperimentProcess{
		ID:         id,
		Meta:       experiment.Meta,
		Name:       experiment.Name,
		Namespace:  util.GenExperimentNamespace(experiment.Name),
		Config:     experiment.Config,
		BoxID:      experiment.BoxID,
		BoxName:    experiment.BoxName,
		Timeout:    experiment.Timeout,
		RunVersion: experiment.RunVersion,
		State:      ExperimentStateNormal,
		Stage:      ExperimentStagePending,
		CatCount:   0,
		Tests:      make(map[string]*TestProcess),
	}

	return experimentClone
}

// IsPreparing returns true if box was preparing
func (experiment *ExperimentProcess) IsPreparing() bool {
	return experiment.Stage == ExperimentStagePreparing
}

// IsRunning returns true if box was running
func (experiment *ExperimentProcess) IsRunning() bool {
	return experiment.Stage == ExperimentStageRunning
}

// IsPending returns true if box was pending
func (experiment *ExperimentProcess) IsPending() bool {
	return experiment.Stage == ExperimentStagePending
}

// IsFinishing returns true if box was finishing
func (experiment *ExperimentProcess) IsFinishing() bool {
	return experiment.Stage == ExperimentStageFinishing
}

// SetStage sets stage
func (experiment *ExperimentProcess) SetStage(stage ExperimentStage) {
	experiment.Stage = stage
}

// IsFinished returns true if box was finished
func (experiment *ExperimentProcess) IsFinished() bool {
	return experiment.Stage == ExperimentStageFinished
}

// IsEnd returns true if box was end
func (experiment *ExperimentProcess) IsEnd() bool {
	return experiment.Stage == ExperimentStageFinished || experiment.IsDestroyed()
}

// IsEnding returns true if box was ending
func (experiment *ExperimentProcess) IsEnding() bool {
	return experiment.Stage == ExperimentStageFinishing
}

// IsDestroyed returns true if experiment is destroyed
func (experiment *ExperimentProcess) IsDestroyed() bool {
	return experiment.Destroyed
}

// BeDestroyed sets true for stopped
func (experiment *ExperimentProcess) BeDestroyed() {
	experiment.Destroyed = true
}

// IsStateNormal returns true if box state is normal
func (experiment *ExperimentProcess) IsStateNormal() bool {
	return experiment.State == ExperimentStateNormal
}

// IsStateAbnormal returns true if box state is abnormal
func (experiment *ExperimentProcess) IsStateAbnormal() bool {
	return experiment.State == ExperimentStateAbnormal
}

// SetStartTime sets start time for experimentProcess
func (experiment *ExperimentProcess) SetStartTime(args ...time.Time) {
	if len(args) > 0 {
		experiment.StartTime = args[0].Format(util.TimeFormat)
		return
	}
	experiment.StartTime = time.Now().Format(util.TimeFormat)
}

// SetEndTime sets end time for experimentProcess
func (experiment *ExperimentProcess) SetEndTime(args ...time.Time) {
	if len(args) > 0 {
		experiment.EndTime = args[0].Format(util.TimeFormat)
		return
	}
	experiment.EndTime = time.Now().Format(util.TimeFormat)
}

// TestsName returns names of the all test
func (experiment *ExperimentProcess) TestsName() []string {
	var names []string
	for _, test := range experiment.Tests {
		names = append(names, test.Name)
	}

	return names
}

// FailedCases returns all cases with failed state
func (experiment *ExperimentProcess) FailedCases() TestProcesses {
	var tests TestProcesses
	for _, t := range experiment.Tests {
		if t.Status == TestStateFailed {
			tests = append(tests, t)
		}
	}

	return tests
}

// ToBrief returns brief information of experiment process
func (experiment *ExperimentProcess) ToBrief() *ExperimentBrief {
	brief := &ExperimentBrief{
		ID:         experiment.ID,
		Name:       experiment.Name,
		BoxID:      experiment.BoxID,
		BoxIndex:   experiment.BoxIndex,
		Ctime:      experiment.Ctime,
		Utime:      experiment.Utime,
		Stage:      experiment.Stage,
		State:      experiment.State,
		Author:     experiment.Author,
		Annotation: experiment.Annotation,
		Destroyed:  experiment.Destroyed,
		DestroyCat: experiment.Config.Misc.DestroyTidbCluster,
	}
	if experiment.Cat != nil {
		brief.Config = experiment.Cat.Config
		brief.RunVersion = experiment.Cat.RunVersion
	}
	if !util.IsEmpty(experiment.Ftime) {
		brief.Utime = experiment.Ftime
	}
	return brief
}

// ExperimentProcesses is an array of ExperimentProcess
type ExperimentProcesses []*ExperimentProcess

// implement sort.Interface
func (eps ExperimentProcesses) Len() int           { return len(eps) }
func (eps ExperimentProcesses) Swap(i, j int)      { eps[i], eps[j] = eps[j], eps[i] }
func (eps ExperimentProcesses) Less(i, j int) bool { return eps[i].Utime > eps[j].Utime }
