package basic

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/mitchellh/mapstructure"

	"github.com/tidbcloud/eks-provider/controllers/workflow/action"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
)

// cancelableSleepAction is a sleep action that supports cancellation
// inputs:
// - duration: the duration to sleep, for example, "10s"
// - cancelable: if true, the action can be cancelled (default: true)
type cancelableSleepAction struct {
	mu           sync.Mutex
	activeSleeps map[string]*sleepContext
	cancelled    map[string]bool
}

type sleepContext struct {
	cancelFunc context.CancelFunc
}

type cancelableSleepInputs struct {
	Duration   string `mapstructure:"duration" json:"duration" validate:"required"`
	Cancelable *bool  `mapstructure:"cancelable" json:"cancelable"`
}

var (
	_ action.Action       = &cancelableSleepAction{}
	_ action.ActionCancel = &cancelableSleepAction{}
)

func init() {
	action.RegisterWorkflowAction(&cancelableSleepAction{
		activeSleeps: make(map[string]*sleepContext),
		cancelled:    make(map[string]bool),
	})
}

func (d *cancelableSleepAction) Name() cloudv1.WorkflowActionName {
	return consts.WorkflowActionCancelableSleep
}

func (d *cancelableSleepAction) PreCheck(ctx context.Context, wf *cloudv1.Workflow, step cloudv1.WorkflowStep) error {
	// check required inputs
	inputs := &cancelableSleepInputs{}
	if err := mapstructure.Decode(step.Inputs.Object, inputs); err != nil {
		return fmt.Errorf("decode inputs: %w", err)
	}
	if _, err := time.ParseDuration(inputs.Duration); err != nil {
		return fmt.Errorf("parse duration %v: %w", inputs.Duration, err)
	}
	return nil
}

func (d *cancelableSleepAction) Handle(ctx context.Context, wf *cloudv1.Workflow, step cloudv1.WorkflowStep) error {
	// Similar to sleep action, Handle does nothing
	// The actual sleep logic is in PostHandle
	return nil
}

func (d *cancelableSleepAction) PostHandle(ctx context.Context, wf *cloudv1.Workflow, step cloudv1.WorkflowStep) error {
	stepStatus, ok := wf.StepStatus(ctx, step.Name)
	if !ok {
		return fmt.Errorf("step status not found: %s", step.Name)
	}

	inputs := &cancelableSleepInputs{}
	if err := mapstructure.Decode(step.Inputs.Object, inputs); err != nil {
		return fmt.Errorf("decode inputs: %w", err)
	}

	// Default to cancelable if not specified
	cancelable := true
	if inputs.Cancelable != nil {
		cancelable = *inputs.Cancelable
	}

	duration, err := time.ParseDuration(inputs.Duration)
	if err != nil {
		return fmt.Errorf("parse duration: %w", err)
	}

	stepKey := fmt.Sprintf("%s/%s", wf.Namespace, step.Name)

	// Check if already cancelled
	d.mu.Lock()
	if d.cancelled[stepKey] {
		d.mu.Unlock()
		return fmt.Errorf("action was cancelled")
	}
	d.mu.Unlock()

	if !cancelable {
		// If not cancelable, behave like regular sleep action
		now := time.Now()
		if now.After(stepStatus.BeginAt.Time.Add(duration)) {
			// reach the target time
			return nil
		}
		return fmt.Errorf("expected to run at %s", stepStatus.BeginAt.Add(duration).Format(time.RFC3339))
	}

	// For cancelable sleep, check if we've reached the duration or been cancelled
	now := time.Now()
	targetTime := stepStatus.BeginAt.Time.Add(duration)

	// Check if cancelled
	d.mu.Lock()
	if d.cancelled[stepKey] {
		d.mu.Unlock()
		return fmt.Errorf("action was cancelled")
	}
	d.mu.Unlock()

	if now.After(targetTime) {
		// reach the target time
		return nil
	}

	return fmt.Errorf("expected to run at %s", targetTime.Format(time.RFC3339))
}

func (d *cancelableSleepAction) Cancel(ctx context.Context, wf *cloudv1.Workflow, step cloudv1.WorkflowStep, dryRun bool) error {
	stepKey := fmt.Sprintf("%s/%s", wf.Namespace, step.Name)

	d.mu.Lock()
	defer d.mu.Unlock()

	// Mark as cancelled
	d.cancelled[stepKey] = true

	// Cancel the active sleep if exists
	if sleepCtx, ok := d.activeSleeps[stepKey]; ok {
		if sleepCtx.cancelFunc != nil {
			sleepCtx.cancelFunc()
		}
	}

	return nil
}

func (d *cancelableSleepAction) PostCancel(ctx context.Context, wf *cloudv1.Workflow, step cloudv1.WorkflowStep) error {
	// Cleanup after cancellation
	stepKey := fmt.Sprintf("%s/%s", wf.Namespace, step.Name)
	d.mu.Lock()
	defer d.mu.Unlock()

	delete(d.activeSleeps, stepKey)
	delete(d.cancelled, stepKey)

	return nil
}
