package model

import (
	"database/sql/driver"
	"encoding/json"
	"fmt"
	"time"

	validation "github.com/Thearas/ozzo-validation/v4"

	"github.com/pingcap/test-infra/common/model/resource"
)

type TestBed struct {
	// Name can be generated by scheduler from GenerateName
	Name string `json:"name,omitempty"`
	// GenerateName is an optional prefix, used by the RMS server, to generate a unique
	// name ONLY IF the Name field has not been provided.
	GenerateName string `json:"generateName,omitempty"`
	// ResourcePool can be re-written by scheduler
	ResourcePool string `json:"resourcePool,omitempty" default:"default"`
	// RmsUrl is the url added by scheduler and used by executor
	RmsUrl string `json:"rmsUrl,omitempty"`
	// If left empty, scheduler will try to estimate it
	PhysicalResourceRequest PhysicalResource    `json:"physicalResourceRequest,omitempty"`
	Items                   []TestBedItem       `json:"items"`
	Qos                     []resource.QosClaim `json:"qos,omitempty"`
	// ReclaimPolicy shows how to clean the whole testbed resources
	ReclaimPolicy resource.ReclaimPolicy `json:"reclaimPolicy"`
}

func (t TestBed) Validate() error {
	return validation.ValidateStruct(&t,
		validation.Field(&t.Name, validation.When(t.GenerateName == "", validation.Required)),
		validation.Field(&t.GenerateName, validation.When(t.Name == "", validation.Required, validation.Length(1, 64))),
		validation.Field(&t.Items, validation.Required),
		validation.Field(&t.Qos),
		validation.Field(&t.ReclaimPolicy),
	)
}

func (s TestBed) Value() (driver.Value, error) {
	return json.Marshal(s)
}

func (s *TestBed) Scan(src interface{}) error {
	var data []byte
	switch v := src.(type) {
	case string:
		data = []byte(v)
	case []byte:
		data = v
	default:
	}
	if len(data) == 0 {
		*s = TestBed{}
		return nil
	}
	return json.Unmarshal(data, s)
}

type TestBedItem struct {
	resource.ResourceHandle `json:",inline"`
	// Spec is the specification of test resource
	// TODO(@mahjonp): add a validator(used on the TCMS server)
	Spec json.RawMessage `json:"spec"`
	// Qos is the quality of service on test resource
	Qos json.RawMessage `json:"qos,omitempty"`
}

func (t TestBedItem) Validate() error {
	return t.ResourceHandle.Validate()
}

type TestBedStatus string

const (
	TestbedHealthy TestBedStatus = "Healthy"
	TestbedFailed  TestBedStatus = "Failed"
)

type TestBedInfoStatus string

const (
	TestBedInfoStatusInitiating TestBedInfoStatus = "INITIATING"
	TestBedInfoStatusReady      TestBedInfoStatus = "READY"
	TestBedInfoStatusReleased   TestBedInfoStatus = "RELEASED"
	TestBedInfoStatusError      TestBedInfoStatus = "ERROR"
)

type TestBedInfo struct {
	ID   ID     `json:"id"`
	Name string `json:"name,omitempty"`
	// URL is the url to the testbed.
	URL             string          `json:"url,omitempty"`
	PlanExecutionID ID              `json:"planExecutionID,omitempty"`
	Type            TestBedInfoType `json:"type"`

	// TestBedInfoStatus = {"INITIATING", "READY", "RELEASED", "ERROR"}
	Status                  TestBedInfoStatus         `json:"status,omitempty"`
	ResourceItems           []TestBedInfoResourceItem `json:"resourceItems,omitempty"`
	PhysicalResourceRequest PhysicalResource          `json:"physicalResourceRequest"`

	CreatedAt time.Time `json:"createdAt,omitempty"`
	UpdatedAt time.Time `json:"updatedAt,omitempty"`
	// LeaseDDL is the time when a testbed will be released
	// LeaseDDL = CreatedAt + ReclaimPolicy.Lease
	LeaseDDL     *time.Time   `json:"leaseDDL,omitempty"`
	InitiatedAt  *time.Time   `json:"initiatedAt,omitempty"`
	ReleasedAt   *time.Time   `json:"releasedAt,omitempty"`
	ReleasedBy   *ID          `json:"releasedBy,omitempty"`
	SetUpLogURL  string       `json:"setUpLogURL,omitempty"`
	ResourcePool ResourcePool `json:"resourcePool,omitempty"`
}

func (ts TestBedInfoStatus) Values() []string {
	return []string{
		string(TestBedInfoStatusInitiating),
		string(TestBedInfoStatusReady),
		string(TestBedInfoStatusReleased),
		string(TestBedInfoStatusError),
	}
}

func (ts TestBedInfoStatus) Validate() error {
	values := []TestBedInfoStatus{
		TestBedInfoStatusInitiating,
		TestBedInfoStatusReady,
		TestBedInfoStatusReleased,
		TestBedInfoStatusError,
	}
	for _, value := range values {
		if value == ts {
			return nil
		}
	}
	return fmt.Errorf("testbedInfo-status should be one of %v", values)
}

type TestBedInfoResourceItem resource.Item

type TestBedInfoType string

const (
	TestBedInfoTypeNormal         TestBedInfoType = "NORMAL"
	TestBedInfoTypeRestoreMonitor TestBedInfoType = "RESTORE_MONITOR"
)

func (ts TestBedInfoType) Values() []string {
	return []string{
		string(TestBedInfoTypeNormal),
		string(TestBedInfoTypeRestoreMonitor),
	}
}
