package instance

import (
	"fmt"

	"github.com/tidbcloud/devops/api"
	"github.com/tidbcloud/devops/entity/event"
	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/pkg/infra/errs"
	"github.com/tidbcloud/devops/pkg/service/operation/actions"
)

type actorWhenRunning struct {
	*baseActor
}

func (a *actorWhenRunning) StartAction(order opent.ActionOrder, maunal bool) error {
	return a.baseActor.startAction(order, maunal, func(action *opent.InstanceAction) error {
		if action.StartedAt != nil {
			return errs.APIErrorf(api.ErrCodeFailedPrecond, "action %s with order %d is already started", action.Name, order)
		}
		return nil
	})
}

func (a *actorWhenRunning) Cancel() error {
	return a.cancel()
}

func (a *actorWhenRunning) FinishAction(order opent.ActionOrder, param FinishInstanceActionParam) error {
	ins := a.Instance

	action := ins.GetAction(order)
	if action == nil {
		return errs.APIErrorf(api.ErrCodeNotFound, "action  with order '%d' is not found", order)
	}
	if action.StartedAt == nil {
		return errs.APIErrorf(api.ErrCodeFailedPrecond, "action '%s' with order '%d' is not started", action.Name, order)
	}

	actionObj, exist := a.Context.ActionRegistry.Get(action.Name)
	if !exist {
		return fmt.Errorf("action '%s' not found in registry", action.Name)
	}
	actionType := actionObj.Metadata().Type

	if actionType == opent.ActionTypeManual {
		if param.AllowManual {
			manualActionObj, exist := a.Context.ActionRegistry.GetAsManual(action.Name)
			if !exist {
				return fmt.Errorf("auto action '%s' not found in registry", action.Name)
			}
			return a.finishManualAction(manualActionObj, action, param.Result, param.Output)
		} else {
			return errs.RetriableErrorf("manual action '%s' is waiting for a person to finish", action.Name)
		}
	}

	if actionType == opent.ActionTypeAuto {
		autoActionObj, exist := a.Context.ActionRegistry.GetAsAuto(action.Name)
		if !exist {
			return fmt.Errorf("auto action '%s' not found in registry", action.Name)
		}
		return a.resultAutoAction(autoActionObj, action)
	}

	return fmt.Errorf("unmatched action type '%s' with param for action '%s'", actionType, action.Name)
}

func (a *actorWhenRunning) resultAutoAction(actionObj actions.AutoAction, action *opent.InstanceAction) error {
	ctx := a.Context

	result, err := actionObj.Result(a.Context, a.Context.Instance)
	if err != nil {
		return err
	}

	if result.Result == opent.ActionStatusRunning { // still running
		return errs.RetriableErrorf("action '%s' is still running", action.Name)
	}

	a.EventDetail.FinishAction = &event.OperationEventFinishAction{
		OperationEventAction: event.OperationEventAction{
			Order: action.Order,
			Name:  action.Name,
		},
		Result: result.Result,
	}

	// update status
	return a.PatchInstance(ctx, UpdateStatusAfterFinishAction(action.Order, result))
}

func (a *actorWhenRunning) finishManualAction(actionObj actions.MaunalAction, action *opent.InstanceAction, result opent.ActionStatus, output string) error {
	ctx := a.Context

	switch result {
	case opent.ActionStatusSucceeded,
		opent.ActionStatusSkipped,
		opent.ActionStatusFailed:
	default:
		return fmt.Errorf("result must be closed status '%s'", result)
	}

	// record event detail
	a.EventDetail.FinishAction = &event.OperationEventFinishAction{
		OperationEventAction: event.OperationEventAction{
			Order: action.Order,
			Name:  action.Name,
		},
		Result: result,
	}

	// callback action
	err := actionObj.OnFinish(ctx, a.Instance, result, output)
	if err != nil {
		return fmt.Errorf("callback action '%s' to finish failed: %s", action.Name, err)
	}

	// update status
	return a.PatchInstance(ctx,
		UpdateStatusAfterFinishAction(action.Order, actions.NewActionResult(result, output, nil)),
	)
}
