package executor

import (
	"fmt"
	"io"
	"time"

	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/base"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/dag"
)

type ActionTask struct {
	ExecTask  *ExecutorTask
	DagAction *dag.DagAction
}

func (task *ActionTask) Id() string {
	id := fmt.Sprintf("%v:%v", task.ExecTask.ExecId, task.DagAction.Id())
	argsId := task.ExecTask.Args.Id()
	if argsId != "" {
		id += ":" + argsId
	}
	return id
}

func (task *ActionTask) String() string {
	return task.Id()
}

func (task *ActionTask) Clone() *ActionTask {
	return &ActionTask{
		ExecTask:  task.ExecTask.Clone(),
		DagAction: task.DagAction.Clone(),
	}
}

func (task *ActionTask) CloneableClone() base.Cloneable {
	return task.Clone()
}

// TODO: Error => Errors for better debug
type ActionTaskStatus struct {
	ActionTask *ActionTask
	StartTime  time.Time
	Elapsed    time.Duration
	Running    bool
	Queued     bool
	Error      error
}

func (status *ActionTaskStatus) Clone() *ActionTaskStatus {
	return &ActionTaskStatus{
		ActionTask: status.ActionTask,
		StartTime:  status.StartTime,
		Elapsed:    status.Elapsed,
		Running:    status.Running,
		Queued:     status.Queued,
		Error:      status.Error,
	}
}

func (status *ActionTaskStatus) CloneableClone() base.Cloneable {
	return status.Clone()
}

func (status *ActionTaskStatus) IsDone() bool {
	return !status.StartTime.IsZero() && !status.Running && status.Error == nil
}

func (status *ActionTaskStatus) HasError() bool {
	return status.Error != nil
}

func (status *ActionTaskStatus) String() string {
	return fmt.Sprintf("%v@%v->(%s) err:%v",
		status.ActionTask.Id(),
		status.StartTime.Format(time.DateTime),
		status.Elapsed.Round(time.Millisecond),
		status.Error)
}

// the `string` is action-id (calculated with args), not action-name
type ActionTaskIdStatus map[string]*ActionTaskStatus

func (status ActionTaskIdStatus) GetStatusesByActionName(actionName string) []*ActionTaskStatus {
	var list []*ActionTaskStatus
	for _, task := range status {
		name := task.ActionTask.DagAction.Action.Name()
		if name == actionName {
			list = append(list, task)
		}
	}
	if len(list) == 0 {
		return nil
	}
	return list
}

func (status ActionTaskIdStatus) SafeGetActionTaskStatus(actionName string) *ActionTaskStatus {
	list := status.GetStatusesByActionName(actionName)
	if len(list) > 1 {
		panic(fmt.Errorf("ActionTaskIdStatus.SafeGetActionTaskStatus: DagAction `%v` has %v running instances (probably good)",
			actionName, len(list)))
	}
	return list[0]
}

func (status ActionTaskIdStatus) IsAllDone(includeUnqueued bool) bool {
	for _, task := range status {
		if !includeUnqueued && !task.Queued {
			continue
		}
		if !task.IsDone() {
			return false
		}
	}
	return true
}

func (status ActionTaskIdStatus) HasError() bool {
	for _, task := range status {
		if task.IsDone() {
			continue
		}
		if task.HasError() {
			return true
		}
	}
	return false
}

func (status ActionTaskIdStatus) Clone() ActionTaskIdStatus {
	clone := make(ActionTaskIdStatus)
	for id, task := range status {
		clone[id] = task.Clone()
	}
	return clone
}

func (status ActionTaskIdStatus) Dump(indent string, w io.Writer) {
	// TODO: dump: debug output
}

type RunningActionTasks map[string]*ActionTask

func NewRunningActionTasks() RunningActionTasks {
	return make(RunningActionTasks)
}

func (tasks RunningActionTasks) Add(task *ActionTask) {
	tasks[task.Id()] = task
}

func (tasks RunningActionTasks) Has(execId string) bool {
	for _, task := range tasks {
		if task.ExecTask.ExecId == execId {
			return true
		}
	}
	return false
}

func (tasks RunningActionTasks) Delete(task *ActionTask) {
	delete(tasks, task.Id())
}
