package types

import (
	"github.com/juju/errors"
	"github.com/pingcap/schrodinger/util"
	"k8s.io/api/core/v1"
)

// Type is app type
type Type string

// app type
const (
	MysqlType      Type = "mysql"
	MariaDBType    Type = "mariadb"
	ToolsType      Type = "tools"
	PromeType      Type = "prome"
	APPCPUCount         = "2"
	APPMemoryCount      = "4G"
)

// EnvVars aliases []v1.EnvVar
type EnvVars []v1.EnvVar

// Actions aliases []Action
type Actions []Action

// APPConfig is app config to create app. now just support mysql
type APPConfig struct {
	ID           int64  `json:"id"`
	Type         Type   `json:"type"`
	Name         string `json:"name"`
	Namespace    string `json:"namespace"`
	Version      string `json:"version"`
	Labels       Labels `json:"label"`
	TestID       int64  `json:"test_id"`
	ExperimentID int64  `json:"experiment_id"`
	BoxID        int64  `json:"box_id"`

	BinaryName string `json:"binary_name"`
	URL        string `json:"url"`
	Args       string `json:"args"`

	ResourceRequirement *ResourceRequirement `json:"resource_requirement"`
	Host                string               `json:"host"`
	ContainerIDs        stringSlice          `json:"container_ids"`
	Port                int32                `json:"port"`
	Envs                EnvVars              `json:"envs"`
	Actions             Actions              `json:"actions"`
	Image               string               `json:"image"`
}

// Verify verifies App
func (app *APPConfig) Verify() error {
	if util.IsEmpty(app.Name) {
		return errors.NotValidf("name is empty")
	}
	if util.IsEmpty(app.Namespace) {
		return errors.NotValidf("namespace is empty")
	}

	if app.Type == MysqlType {
		return errors.Trace(app.verifyMysql())
	}

	if app.Type == MariaDBType {
		return errors.Trace(app.verifyMariaDB())
	}

	if app.Type == ToolsType {
		return errors.Trace(app.verifyTools())
	}

	return nil
}

// SetDefaultValue sets default value
func (app *APPConfig) SetDefaultValue() {
	if util.IsEmpty(string(app.Type)) {
		app.Type = MysqlType
	}
	if app.ResourceRequirement == nil {
		app.ResourceRequirement = &ResourceRequirement{
			CPU:    APPCPUCount,
			Memory: APPMemoryCount,
		}
		return
	}
	app.ResourceRequirement.SetDefaultValue(APPCPUCount, APPMemoryCount)
}

// IsMysql returns true if app is mysql
func (app *APPConfig) IsMysql() bool {
	return app.Type == MysqlType
}

// IsMariaDB returns true if app is maridb
func (app *APPConfig) IsMariaDB() bool {
	return app.Type == MariaDBType
}

// IsTool returns true if app is a tool
func (app *APPConfig) IsTool() bool {
	return app.Type == ToolsType
}

// IsProme returns true if app is a prome
func (app *APPConfig) IsProme() bool {
	return app.Type == PromeType
}

func (app *APPConfig) verifyMysql() error {
	if util.IsEmpty(app.Version) {
		return errors.New("app version should provide")
	}
	return nil
}

func (app *APPConfig) verifyMariaDB() error {
	if util.IsEmpty(app.Version) {
		return errors.New("app version should provide")
	}
	return nil
}

func (app *APPConfig) verifyTools() error {
	if util.IsEmpty(app.BinaryName) {
		return errors.New("app binary name should provide")
	}

	if util.IsEmpty(app.URL) {
		return errors.New("app download url should not be empty")
	}
	return nil
}

// APPProcess is an app instance in a box
type APPProcess struct {
	ID        int64       `json:"id"`
	Name      string      `json:"name"`
	Config    *APPConfig  `json:"app_config"`
	Report    *TestReport `json:"report"`
	Ctime     string      `json:"create_time"`
	Utime     string      `json:"update_time"`
	Status    TestState   `json:"status"`
	Mark      Mark        `json:"mark"`
	Namespace string      `json:"namespace"`
}

// Clone clones appProcess
func (a *APPProcess) Clone() *APPProcess {
	ap := &APPProcess{}
	if err := util.Clone(ap, a); err != nil {
		return nil
	}

	return ap
}

// UpdateReport updates report of the app
func (a *APPProcess) UpdateReport() {
	report := &TestReport{
		ExperimentID: a.Config.ExperimentID,
		Status:       a.Status,
		Ctime:        a.Ctime,
		Utime:        a.Utime,
		Name:         a.Config.Name,
		Namespace:    a.Config.Namespace,
		PodIP:        a.Config.Host,
		PodName:      a.Config.Name,
	}

	if len(a.Config.ContainerIDs) > 0 {
		report.ContainerID = a.Config.ContainerIDs[0]
	}

	if !util.IsEmpty(report.ContainerID) {
		report.LogPath = util.GenLogURL(report.Namespace, report.PodName)
	}

	a.Report = report
}

// APPProcesses is a set of test process
type APPProcesses []*APPProcess

// GetReports returns all reports of apps
func (a APPProcesses) GetReports() []*TestReport {
	reports := make([]*TestReport, 0, len(a))
	for _, app := range a {
		reports = append(reports, app.Report)
	}

	return reports
}

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