package steps

import (
	"context"
	"fmt"
	"time"

	"github.com/pingcap/errors"
	"github.com/pingcap/log"
	apiresource "github.com/pingcap/test-infra/common/model/resource"
	"github.com/pingcap/test-infra/sdk/core"
	"github.com/pingcap/test-infra/sdk/resource"
	"go.uber.org/zap"
)

type workloadChaosTask struct {
	duration     time.Duration
	workload     *apiresource.ResourceHandle
	spec         resource.ExperimentSpec
	selectOption resource.WorkloadNodeSelectorOpts
}

func (w *workloadChaosTask) Execute(ctx context.Context) error {
	resolved, err := core.ResolveResourceHandle(w.workload)
	if err != nil {
		return errors.Trace(err)
	}

	tc := resolved.(resource.WorkloadNode)
	selector, err := tc.ChaosSelect(w.selectOption)
	if err != nil {
		return errors.Trace(err)
	}

	chaosOption := resource.InjectChaosOptions{
		Selector:       selector,
		ExperimentSpec: w.spec,
	}

	chaosID, err := tc.ChaosClient().Create(chaosOption)
	if err != nil {
		log.Info("create workload chaos failed", zap.Error(err), zap.Reflect("chaosOption", chaosOption))
		return errors.Trace(err)
	}
	defer func() {
		if err := tc.ChaosClient().Delete(chaosID); err != nil {
			log.Warn("delete chaos failed", zap.Error(err), zap.Reflect("chaosOption", chaosOption))
		}
	}()

	if chaosID == nil {
		log.Info("create workload workload chao failed", zap.Reflect("chaosOption", chaosOption), zap.Reflect("chaosID", chaosID))
		return fmt.Errorf("create chaos failed, option=%+v", chaosOption)
	}

	time.Sleep(w.duration)

	log.Info("run workload chaos finished")

	return nil

}

func (w *workloadChaosTask) String() string {
	return fmt.Sprintf("chaosTask: spec=%+v, duration=%+v", w.spec, w.duration)
}

func NewWorkloadChaosTask(workload *apiresource.ResourceHandle, selectOption resource.WorkloadNodeSelectorOpts, spec resource.ExperimentSpec, duration time.Duration) *workloadChaosTask {
	return &workloadChaosTask{
		duration:     duration,
		workload:     workload,
		selectOption: selectOption,
		spec:         spec,
	}
}

type workloadFailureChaos struct {
	workload *apiresource.ResourceHandle
	duration time.Duration
}

func (w *workloadFailureChaos) Execute(ctx context.Context) error {
	return NewWorkloadChaosTask(w.workload, resource.WorkloadNodeSelectorOpts{All: true}, &resource.FailureExperimentSpec{}, w.duration).
		Execute(ctx)
}

func (w *workloadFailureChaos) String() string {
	return fmt.Sprintf("workloadFailureChaos: duration=%+v", w.duration)
}

func NewWorkloadFailureChaos(workload *apiresource.ResourceHandle, duration time.Duration) *workloadFailureChaos {
	return &workloadFailureChaos{
		workload: workload,
		duration: duration,
	}
}

func (b *Builder) WorkloadFailureChaos(workload *apiresource.ResourceHandle, duration time.Duration) *Builder {
	b.steps = append(b.steps, NewWorkloadFailureChaos(workload, duration))
	return b
}
