package deploy

import (
	"context"
	"fmt"
	"time"

	"github.com/go-logr/logr"

	"github.com/tidbcloud/observability/agent-control-server/pkg/metrics"
)

type Deployer struct {
	logger       logr.Logger
	tasks        []Task
	results      []Result
	ignoreDelete []string
}

func NewDeployer(tasks []Task, ignoreDelete []string, logger logr.Logger) *Deployer {
	return &Deployer{
		tasks:        tasks,
		logger:       logger,
		results:      []Result{},
		ignoreDelete: ignoreDelete,
	}
}

func (d *Deployer) Deploy(ctx context.Context) []Result {
	for _, t := range d.tasks {
		r := d.execute(ctx, t, t.Create, stepCreate)
		if r.state == stateDone || r.state == stateError {
			r = d.execute(ctx, t, t.PostCreate, stepPostCreate)
		}
		if r.state == statePanic {
			break
		}
	}
	return d.results
}

func (d *Deployer) Destroy(ctx context.Context) []Result {
	// Execute the task in reverse and move first to the last.
	// Guardian task must be executed first.
	d.tasks = append(d.tasks, d.tasks[0])
	ignoreDelete := func(t string) bool {
		for _, v := range d.ignoreDelete {
			if v == t {
				return true
			}
		}
		return false
	}
	for index := range d.tasks[1:] {
		t := d.tasks[len(d.tasks)-index-1]
		if ignoreDelete(t.Name()) {
			continue
		}
		r := d.execute(ctx, t, t.Delete, stepDelete)
		if r.state == stateDone || r.state == stateError {
			r = d.execute(ctx, t, t.PostDelete, stepPostDelete)
		}
		if r.state == statePanic {
			break
		}
	}
	return d.results
}

func (d *Deployer) execute(ctx context.Context, task Task, f func(context.Context) Result, step stepType) Result {
	d.logger.Info(fmt.Sprintf("start to execute task (%s/%s)", step, task.Name()))
	start := time.Now()
	result := f(ctx)

	lbs := []string{
		task.ClusterMeta().Agent,
		task.Name(),
		task.ClusterMeta().K8sClusterName,
		task.ClusterMeta().TenantID,
		task.ClusterMeta().ApplicationID,
		string(step),
	}
	metrics.ObserveControllerHandledTaskDuration(lbs, time.Since(start).Seconds())

	takes := time.Since(start)
	result.task = task.Name()
	result.stepType = step
	switch result.state {
	case stateDone:
		d.logger.Info(fmt.Sprintf("execute task (%s/%s) done, take: %s", step, task.Name(), takes))
		metrics.IncControllerHandledTaskCounter(append(lbs, string(result.state)))
	default:
		d.logger.Info(fmt.Sprintf("execute task (%s/%s) unfinished, take: %s", step, task.Name(), takes))
		metrics.IncControllerHandledTaskCounter(append(lbs, string(result.state)))
	}
	d.results = append(d.results, result)
	return result
}
