package deploy

import (
	"fmt"
	"time"

	"go.uber.org/multierr"
	ctrl "sigs.k8s.io/controller-runtime"
)

type state string

const (
	// Task finished with no errors.
	stateDone state = "Done"
	// Task finished with errors.
	stateError state = "Error"
	// Task finished with errors. The entire deployment should stop.
	// TODO(tangent): Consider how reconcile requeue should work during panic
	statePanic state = "Panic"
)

type stepType string

const (
	stepCreate     stepType = "Create"
	stepPostCreate stepType = "PostCreate"
	stepDelete     stepType = "Delete"
	stepPostDelete stepType = "PostDelete"
)

type Result struct {
	task     string
	stepType stepType

	state state
	err   error
}

func (r Result) Error() error {
	switch r.state {
	case stateError, statePanic:
		return fmt.Errorf("%s: %v", r.stepType, r.err)
	}
	return nil
}

func (r Result) IsDoneState() bool {
	return r.state == stateDone
}

func (r Result) WithRequeueAfter(d time.Duration) Result {
	r.err = NewRetriableError(r.err, ctrl.Result{Requeue: true, RequeueAfter: d})
	return r
}

func (r Result) WithName(name string) Result {
	r.task = name
	return r
}

func Done() Result {
	return Result{
		state: stateDone,
	}
}

func Panic(err error) Result {
	return Result{
		state: statePanic,
		err:   err,
	}
}

func PanicOrDone(err error) Result {
	if err == nil {
		return Done()
	}
	return Panic(err)
}

func Error(err error) Result {
	return Result{
		state: stateError,
		err:   err,
	}
}

func ErrorOrDone(err error) Result {
	if err == nil {
		return Done()
	}
	return Error(err)
}

func minResult(lhs ctrl.Result, rhs ctrl.Result) ctrl.Result {
	if lhs.RequeueAfter > 0 {
		lhs.Requeue = true
	}
	if rhs.RequeueAfter > 0 {
		rhs.Requeue = true
	}

	if lhs.Requeue != rhs.Requeue {
		if lhs.Requeue {
			return lhs
		}
		return rhs
	}

	if lhs.RequeueAfter < rhs.RequeueAfter {
		return lhs
	}
	return rhs
}

// HasPanic reports whether results contain a Panic-state task.
// The deployer halts the pipeline on the first Panic, so at most one
// Panic result exists per reconcile.
func HasPanic(results []Result) bool {
	for _, r := range results {
		if r.state == statePanic {
			return true
		}
	}
	return false
}

// MinRequeue iteratures tasks' results, returns the minimum requeue
// duration and the errs from all tasks.
func MinRequeue(results []Result) (res ctrl.Result, errs error, retriableErrs error) {
	for _, r := range results {
		if err := r.err; err != nil {
			tmpRes := ShouldRequeue(err)
			res = minResult(tmpRes, res)

			if RequeueOnly(err) {
				continue
			}

			err := fmt.Errorf("%s: %w", r.task, err)
			if !tmpRes.IsZero() {
				retriableErrs = multierr.Append(retriableErrs, err)
			} else {
				errs = multierr.Append(errs, err)
			}
		}
	}
	return
}
