package model

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/tidbcloud/pov-service/internal/common/constant"
	"github.com/tidbcloud/pov-service/internal/pkg/utils"
	"github.com/tidbcloud/pov-service/internal/pkg/uuid"
)

type ExecBase struct {
	CreatedAt  time.Time  `json:"createdAt,omitempty"`
	UpdatedAt  *time.Time `json:"updatedAt,omitempty"`
	StartedAt  *time.Time `json:"startedAt,omitempty"`
	FinishedAt *time.Time `json:"finishedAt,omitempty"`
	Status     ExecStatus `json:"status,omitempty"`
	Message    string     `json:"message,omitempty"`
}

func (w ExecBase) IsCompleted() bool {
	return w.Status == ExecStatusCompleted
}

func (w ExecBase) IsFinished() bool {
	return w.Status == ExecStatusCompleted || w.Status == ExecStatusFailed || w.Status == ExecStatusStopped
}

func (w ExecBase) IsAbnormalFinished() bool {
	return w.Status == ExecStatusFailed || w.Status == ExecStatusStopped
}

func (w ExecBase) IsActive() bool {
	return !w.IsFinished()
}

func (w ExecBase) IsOnTheFly() bool {
	return w.Status == ExecStatusRunning || w.Status == ExecStatusStopping
}

func (w *ExecBase) Start() {
	now := time.Now().UTC()
	w.StartedAt = &now
}

func (w ExecBase) ExecDuration() time.Duration {
	if w.StartedAt == nil {
		return 0
	}
	if w.FinishedAt != nil {
		return w.FinishedAt.Sub(*w.StartedAt)
	}

	// @MimeLyc: UpdatedAt is the time DB records being updated, so I prefer to use current time instead.
	return time.Since(*w.StartedAt)
}

// WorkloadTaskBase is a non-generic version for common operations
type WorkloadTaskBase struct {
	// ID: leave the database to generate the ID
	ID int64 `json:"ID"`
	// WorkloadID int64 `json:"workloadID"`
	Typ WorkloadTaskType `json:"type"`
	ExecBase

	Progress float64 `json:"progress,omitempty"`

	// Phase tracks the internal phase of the task for better recovery and concurrency control
	Phase WorkloadTaskPhase `json:"phase,omitempty"`

	EstimatedDurationMin int64 `json:"estimatedDurationMin,omitempty"`

	// RelatedServiceID is the ID of the underlying service, for example,
	// the ID of the workflow execution in tcms, or the ID of the workload execution in tiworkload
	RelatedServiceID string `json:"-"`
}

func (w *WorkloadTaskBase) SetProgress(progress float64) {
	if progress > 1.0 {
		progress = 1.0
	}
	w.Progress = progress
}

type WorkloadTask[T any] struct {
	WorkloadTaskBase
	Result T `json:"-"`
}

func NewPendingTask(typ WorkloadTaskType) WorkloadTask[any] {
	return WorkloadTask[any]{
		WorkloadTaskBase: WorkloadTaskBase{
			Typ:   typ,
			Phase: WorkloadTaskPhasePending,
			ExecBase: ExecBase{
				Status: ExecStatusPending,
			},
		},
	}
}

func NewCreateClusterTask() WorkloadTask[any] {
	return NewPendingTask(WorkloadTaskTypeCreateCluster)
}

func NewPrepareTask() WorkloadTask[any] {
	return NewPendingTask(WorkloadTaskTypePrepareData)
}

func NewRunTrafficTask() WorkloadTask[any] {
	return NewPendingTask(WorkloadTaskTypeRunTraffic)
}

// CreateClusterResult represents the result of create cluster task
type CreateClusterResult struct {
	Cluster *TiDBCluster `json:"cluster"`
}

// PrepareDataResult represents the result of prepare data task
type PrepareDataResult struct {
	DataSize   int64 `json:"dataSize"`   // TODO
	TableCount int   `json:"tableCount"` // TODO

	DBHost string `json:"db_host"`
	DBPort int    `json:"db_port"`
	PDHost string `json:"pd_host"`
	PDPort int    `json:"pd_port"`

	// Since cluster is registed to tiworkload in prepare data task
	// The clusterID can only be set here
	TiWorkloadClusterID    string `json:"tiWorkloadClusterID"`
	TiWorkloadWorkloadName string `json:"tiWorkloadWorkloadID"`
}

func (p PrepareDataResult) HasConnection() bool {
	return p.DBHost != "" && p.DBPort != 0
}

// RunTrafficResult represents the result of run traffic task
type RunTrafficResult struct {
}

type WorkloadTest struct {
	// TODO: leave the database to generate the ID
	ID   string `json:"id"`
	Name string `json:"name"`

	// Original fields from tcms workflow
	WorkloadID   int64          `json:"workloadID"`
	WorkloadName string         `json:"workloadName,omitempty"`
	Params       WorkloadParams `json:"params,omitempty"`

	// Derived fields
	ExecBase

	// Progress is the ratio of completed tasks/total tasks
	Progress float64           `json:"progress,omitempty"`
	Phase    WorkloadTestPhase `json:"phase,omitempty"`
	IsAuto   bool              `json:"isAuto"`

	// User who started this workload test
	StartedBy string `json:"startedBy,omitempty"`

	Cluster *TiDBCluster        `json:"cluster,omitempty"`
	Tasks   []WorkloadTask[any] `json:"tasks,omitempty"`
	// TODO
	/*
		Monitor
		Comment
		Cost
		CostPerSec
	*/
}

func (w *WorkloadTest) AppendTask(
	task WorkloadTask[any]) {
	w.Tasks = append(w.Tasks, task)
}

func (w *WorkloadTest) IsClusterCreated() bool {
	for _, task := range w.Tasks {
		if task.Typ == WorkloadTaskTypeCreateCluster && task.Status == ExecStatusCompleted {
			return true
		}
	}
	return false
}

func (w *WorkloadTest) IsDataPrepared() bool {
	for _, task := range w.Tasks {
		if task.Typ == WorkloadTaskTypePrepareData && task.Status == ExecStatusCompleted {
			return true
		}
	}
	return false
}

func (w *WorkloadTest) GetPrepareDataTask() *WorkloadTask[any] {
	for _, task := range w.Tasks {
		if task.Typ == WorkloadTaskTypePrepareData {
			return &task
		}
	}
	return nil
}

func (w *WorkloadTest) GetPrepareDataResult() (PrepareDataResult, error) {
	prepareTask := w.GetPrepareDataTask()
	if prepareTask == nil {
		err := fmt.Errorf("prepare data task not found")
		return PrepareDataResult{}, err
	}

	prepareResult, ok := prepareTask.Result.(PrepareDataResult)
	if !ok {
		return PrepareDataResult{}, fmt.Errorf("invalid prepare data result")
	}

	return prepareResult, nil
}

func (w *WorkloadTest) CurrentPhaseTask() *WorkloadTask[any] {
	switch w.Phase {
	case WorkloadTestPhaseCreateCluster:
		for i, task := range w.Tasks {
			if task.Typ == WorkloadTaskTypeCreateCluster {
				return &w.Tasks[i]
			}
		}
	case WorkloadTestPhasePrepareData:
		for i, task := range w.Tasks {
			if task.Typ == WorkloadTaskTypePrepareData {
				return &w.Tasks[i]
			}
		}
	case WorkloadTestPhaseRunWorkload:
		for i, task := range w.Tasks {
			if task.Typ == WorkloadTaskTypeRunTraffic {
				return &w.Tasks[i]
			}
		}
	}
	return nil
}

func (w *WorkloadTest) OnStart() {
	w.Status = ExecStatusRunning
	w.Phase = WorkloadTestPhaseCreateCluster
	w.StartedAt = utils.TimePointer(time.Now().UTC())
}

func (w *WorkloadTest) ShouldComplete() bool {
	if w.Phase == WorkloadTestPhaseRunWorkload && w.CurrentPhaseTask().Status == ExecStatusCompleted {
		return true
	}
	return false
}

func (w *WorkloadTest) HasAbnormalTask() (bool, WorkloadTaskBase) {
	for _, task := range w.Tasks {
		if task.Status == ExecStatusFailed || task.Status == ExecStatusStopped {
			return true, task.WorkloadTaskBase
		}
	}
	return false, WorkloadTaskBase{}
}

func (w *WorkloadTest) OnFinish() {
	w.FinishedAt = utils.TimePointer(time.Now().UTC())
	w.Phase = WorkloadTestPhaseFinished
}

func (w *WorkloadTest) OnComplete() {
	w.OnFinish()
	w.Status = ExecStatusCompleted
}

func (w *WorkloadTest) OnError(err error) {
	w.OnFinish()
	w.Status = ExecStatusFailed
	w.Message = err.Error()
}

func (w *WorkloadTest) OnStop() {
	w.OnFinish()
	w.Status = ExecStatusStopped
}

func (w *WorkloadTest) AppendTestbedParam(testbed string) {
	w.Params = append(w.Params, WorkloadParam{
		Name:  "testbed_name",
		Value: testbed,
	})
}

func (w *WorkloadTest) GetParam(key string) (value any, ok bool) {
	for _, param := range w.Params {
		if param.Name == key {
			return param.Value, true
		}
	}
	return nil, false
}

func (w *WorkloadTest) GetDurationMinConf() (value int, ok bool) {
	v, ok := w.GetParam(string(constant.Duration))
	if !ok {
		return
	}

	if _, ok = v.(int); ok {
		return v.(int), ok
	} else if _, ok = v.(float64); ok {
		return int(v.(float64)), ok
	} else if _, ok = v.(string); ok {
		v, err := strconv.Atoi(v.(string))
		if err != nil {
			return 0, false
		}
		return v, ok
	}
	return
}

func (w *WorkloadTest) Ready2PrepareData() bool {
	if !w.IsOnTheFly() {
		return false
	}
	currentTask := w.CurrentPhaseTask()
	if currentTask.Typ != WorkloadTaskTypeCreateCluster || !currentTask.IsCompleted() {
		return false
	}

	return true
}

func (w *WorkloadTest) Ready2RunTraffic() bool {
	if !w.IsOnTheFly() {
		return false
	}
	currentTask := w.CurrentPhaseTask()
	if currentTask.Typ != WorkloadTaskTypePrepareData || !currentTask.IsCompleted() {
		return false
	}

	return true
}

func (w *WorkloadTest) ProceedPrepareData() {
	w.Phase = WorkloadTestPhasePrepareData
	w.AppendTask(NewPrepareTask())
}

func (w *WorkloadTest) ProceedCreateCluster() {
	w.Phase = WorkloadTestPhaseCreateCluster
	w.Status = ExecStatusRunning
	w.AppendTask(NewCreateClusterTask())
}

func (w *WorkloadTest) ProceedRunTraffic() {
	w.Phase = WorkloadTestPhaseRunWorkload
	w.AppendTask(NewRunTrafficTask())
}

func (w *WorkloadTest) StartedByPrefix() string {
	if !strings.Contains(w.StartedBy, "@") {
		return w.StartedBy
	}
	return w.StartedBy[:strings.Index(w.StartedBy, "@")]
}

func NewWorkloadTest(
	WorkloadTestName string,
	WorkloadID int64,
	Params WorkloadParams,
	IsAuto bool,
	StartedBy string,
) WorkloadTest {
	id := uuid.NewString()
	return WorkloadTest{
		ID:         id,
		Name:       WorkloadTestName,
		WorkloadID: WorkloadID,
		Params:     Params,
		StartedBy:  StartedBy,
		ExecBase: ExecBase{
			Status: ExecStatusPending,
		},
		Phase:  WorkloadTestPhasePending,
		IsAuto: IsAuto,
		Tasks:  make([]WorkloadTask[any], 0),
	}
}
