package instance

import (
	"fmt"
	"time"

	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
	"github.com/tidbcloud/devops/pkg/service/operation/actions"
	"github.com/tidbcloud/devops/pkg/service/operation/repo"
)

func CheckActionReady(ins *opent.Instance, action *opent.InstanceAction, maunal bool) []string {
	now := time.Now()
	reasons := []string{}

	// is time to execute this action
	switch action.StartPolicy {
	case opent.ActionStartPolicyManual:
		if !maunal {
			reasons = append(reasons, "must start manually")
		}
	case opent.ActionStartPolicyImmediate:
		// always ready
	case opent.ActionStartPolicySchedule:
		if !maunal && *action.StartScheduleAt >= now.Unix() {
			reasons = append(reasons, fmt.Sprintf("schedule time %s not reached", time.Unix(*action.StartScheduleAt, 0)))
		}
	default:
		reasons = append(reasons, fmt.Sprintf("unknown start policy %s", action.StartPolicy))
	}

	// check if all previous actions are finished and succeeded
	for _, prevAction := range ins.Actions {
		if prevAction.Order >= action.Order {
			break
		}

		if prevAction.Status != opent.ActionStatusSucceeded && prevAction.Status != opent.ActionStatusSkipped {
			reasons = append(reasons, fmt.Sprintf("previous action %s not succeeded", prevAction.Name))
			break
		}
	}

	return reasons
}

func UpdateStatusAfterStartAction(order opent.ActionOrder, result *actions.ActionResult) repo.PatchInstance {
	now := time.Now()
	return func(current *opent.Instance) error {
		// update action status
		for i := range current.Actions {
			if current.Actions[i].Order != order {
				continue
			}

			switch result.Result {
			case opent.ActionStatusRunning, opent.ActionStatusSucceeded, opent.ActionStatusSkipped:
				current.Actions[i].StartedAt = pointer.Pointer(now.Unix())
			}

			switch result.Result {
			case opent.ActionStatusSucceeded, opent.ActionStatusFailed, opent.ActionStatusSkipped:
				current.Actions[i].FinishedAt = pointer.Pointer(now.Unix())
			}

			current.Actions[i].Status = result.Result
			if len(result.ActionOutput) > 2040 { // truncate output to avoid too long
				result.ActionOutput = result.ActionOutput[:2040] + "..."
			}

			current.Actions[i].Status = result.Result
			current.Actions[i].Output = result.ActionOutput
		}

		// update instance outputs
		for name, value := range result.InstanceOutputs {
			if current.Outputs == nil {
				current.Outputs = map[opent.OutputName]string{}
			}
			current.Outputs[name] = value
		}

		// update instance phase
		updateInstancePhaseAfterAction(current, now)

		return nil
	}
}

func UpdateStatusAfterFinishAction(order opent.ActionOrder, result *actions.ActionResult) repo.PatchInstance {
	now := time.Now()
	return func(current *opent.Instance) error {
		// update action status
		for i := range current.Actions {
			if current.Actions[i].Order != order {
				continue
			}

			switch result.Result {
			case opent.ActionStatusSucceeded, opent.ActionStatusFailed, opent.ActionStatusSkipped:
				current.Actions[i].FinishedAt = pointer.Pointer(now.Unix())
			}

			current.Actions[i].Status = result.Result
			if len(result.ActionOutput) > 2040 { // truncate output to avoid too long
				result.ActionOutput = result.ActionOutput[:2040] + "..."
			}

			current.Actions[i].Output = result.ActionOutput
		}

		// update instance outputs
		for name, value := range result.InstanceOutputs {
			if current.Outputs == nil {
				current.Outputs = map[opent.OutputName]string{}
			}
			current.Outputs[name] = value
		}

		// update instance phase
		updateInstancePhaseAfterAction(current, now)

		return nil
	}
}

func updateInstancePhaseAfterAction(ins *opent.Instance, now time.Time) {
	ins.Phase = opent.InstancePhaseRunning
	allSucceeded := true
	for _, action := range ins.Actions {
		// * If any action is failed, then the instance is failed
		if action.Status == opent.ActionStatusFailed {
			ins.Phase = opent.InstancePhaseFailed
			return
		}

		if action.Status != opent.ActionStatusSucceeded && action.Status != opent.ActionStatusSkipped {
			allSucceeded = false
		}
	}

	// * If all actions are succeeded, then the instance is succeeded
	if allSucceeded {
		ins.Phase = opent.InstancePhaseSucceeded
		ins.ClosedAt = pointer.Pointer(now.Unix())
		return
	}

}
