package deploy

import (
	"context"

	"github.com/go-logr/logr"
	ctrl "sigs.k8s.io/controller-runtime"
)

type Deployer struct {
	logger logr.Logger

	tasks    []Task
	prestart []Task
	poststop []Task
}

func NewDeployer(tasks, prestart, poststop []Task, logger logr.Logger) *Deployer {
	return &Deployer{
		tasks:    tasks,
		prestart: prestart,
		poststop: poststop,
		logger:   logger,
	}
}

// Deploy executes the methods in the following order:
// 1. Create methods of each pre-start tasks
// 2. Create and PostCreate methods of each main tasks
// 3. PostCreate methods of each post-stop tasks
func (d *Deployer) Deploy(ctx context.Context) (ctrl.Result, error) {
	var results []Result
	for _, t := range d.prestart {
		d.logger.Info("Running PreStart:", "name", t.Name())
		results = append(results, t.Create(ctx))
	}

	for _, t := range d.tasks {
		d.logger.Info("Running Create:", "name", t.Name())
		r := t.Create(ctx).WithName(t.Name())
		results = append(results, r)
		if !r.skipped {
			d.logger.Info("Running PostCreate:", "name", t.Name())
			r := t.PostCreate(ctx).WithName(t.Name())
			results = append(results, r)
		} else {
			d.logger.Info("Create task skipped", "task", t.Name(), "reason", r.reason)
		}
	}

	for _, t := range d.poststop {
		d.logger.Info("Running PostCreate:", "name", t.Name())
		results = append(results, t.PostCreate(ctx))
	}
	d.logger.Info("Deploy finished")
	return d.parseResults(results)
}

// Destroy executes the methods in the following order:
// 1. Delete methods of each pre-start tasks
// 2. Delete and PostDelete methods of each main tasks
// 3. PostDelete methods of each post-stop tasks
func (d *Deployer) Destroy(ctx context.Context) (ctrl.Result, error) {
	var results []Result
	for _, t := range d.prestart {
		results = append(results, t.Create(ctx))
	}

	for _, t := range d.tasks {
		r := t.Delete(ctx).WithName(t.Name())
		results = append(results, r)
		if !r.skipped {
			r := t.PostDelete(ctx).WithName(t.Name())
			results = append(results, r)
		}
	}

	for _, t := range d.poststop {
		results = append(results, t.PostDelete(ctx))
	}

	return d.parseResults(results)
}

func (d *Deployer) parseResults(results []Result) (ctrl.Result, error) {
	// TODO: Pop up last errors
	// TODO(tangent): Shall we add defaultWaitInterval?
	var r ctrl.Result
	var errs []error
	for _, result := range results {
		if result.error != nil {
			d.logger.Info("Error from task", "name", result.fromTask, "error", result.error)
			errs = append(errs, result.error)
		}
	}
	if len(errs) != 0 {
		return r, errs[0]
	}
	return r, nil
}
