package task

import (
	"context"
	"fmt"
	"time"

	"github.com/tidbcloud/pov-service/internal/model"
	"github.com/tidbcloud/pov-service/internal/pkg/log"
)

const EstimatedCreationDuration = 20 * time.Minute

const (
	CreateClusterTriggerProcessorName = "create-cluster-trigger"
	CreateClusterRunningProcessorName = "create-cluster-running"
)

// CreateClusterTriggerProcessor handles triggering create cluster tasks
type CreateClusterTriggerProcessor struct {
	BaseProcessor
}

func NewCreateClusterTriggerProcessor(manager *ProcessorManager) *CreateClusterTriggerProcessor {
	return &CreateClusterTriggerProcessor{BaseProcessor{manager}}
}

func (p *CreateClusterTriggerProcessor) GetName() string {
	return CreateClusterTriggerProcessorName
}

func (p *CreateClusterTriggerProcessor) CanProcess(test model.WorkloadTest, taskPhase model.WorkloadTaskPhase) bool {
	return test.Phase == model.WorkloadTestPhaseCreateCluster &&
		test.Status == model.ExecStatusRunning &&
		taskPhase == model.WorkloadTaskPhasePending
}

func (p *CreateClusterTriggerProcessor) Process(
	ctx context.Context,
	test model.WorkloadTest,
) (model.WorkloadTest, error) {
	log.Log.Infof("Triggering create cluster for test %s", test.ID)

	currentTask := test.CurrentPhaseTask()
	if currentTask == nil {
		return test, fmt.Errorf("no current task found")
	}

	// Update task phase to triggering
	p.setTaskTriggering(currentTask)

	// Trigger create cluster
	taskBase, err := p.manager.gateway.TriggerCreateCluster(
		ctx,
		test.Params,
	)
	if err != nil {
		// Don't return error for trigger processors to avoid retrying non-idempotent operations
		// Instead, mark the task as failed and let it be handled by phase transition processor
		log.Log.Errorf(err, "failed to trigger create cluster for test %s", test.ID)
		p.setTaskFailed(currentTask, err)
		return test, nil // Return nil to commit the transaction and avoid retry
	}

	// Update task with external service info
	currentTask.Typ = model.WorkloadTaskTypeCreateCluster
	currentTask.EstimatedDurationMin = int64(EstimatedCreationDuration.Minutes())
	p.setTaskRunning(currentTask, taskBase)

	return test, nil
}

// CreateClusterRunningProcessor handles running create cluster tasks
type CreateClusterRunningProcessor struct {
	BaseProcessor
}

func NewCreateClusterRunningProcessor(manager *ProcessorManager) *CreateClusterRunningProcessor {
	return &CreateClusterRunningProcessor{BaseProcessor{manager}}
}

func (p *CreateClusterRunningProcessor) GetName() string {
	return CreateClusterRunningProcessorName
}

func (p *CreateClusterRunningProcessor) CanProcess(test model.WorkloadTest, taskPhase model.WorkloadTaskPhase) bool {
	return test.Phase == model.WorkloadTestPhaseCreateCluster &&
		test.IsOnTheFly() &&
		taskPhase == model.WorkloadTaskPhaseRunning
}

func (p *CreateClusterRunningProcessor) Process(
	ctx context.Context, test model.WorkloadTest,
) (model.WorkloadTest, error) {
	log.Log.Infof("Polling create cluster for test %s", test.ID)

	currentTask := test.CurrentPhaseTask()
	if currentTask == nil {
		return test, fmt.Errorf("no current task found")
	}

	// Poll the task
	taskBase, cluster, err := p.manager.gateway.PollCreateClusterTask(
		ctx, test.Cluster, currentTask.WorkloadTaskBase)
	if err != nil {
		log.Log.Errorf(err, "failed to poll create cluster task for test %s", test.ID)
		return test, nil // Don't fail, just continue polling
	}
	test.Cluster = cluster.Cluster
	if taskBase.IsOnTheFly() {
		passedDuration := taskBase.ExecDuration().Minutes()
		progress := float64(passedDuration) / float64(EstimatedCreationDuration.Minutes())
		if progress >= 1 {
			progress = 0.99
		}
		taskBase.SetProgress(progress)
	}

	// Update task with latest info
	p.updateTaskFromRunning(currentTask, taskBase)

	// Check if completed
	switch taskBase.Status {
	case model.ExecStatusCompleted:
		currentTask.Result = cluster
		p.setTaskCompleted(currentTask)
		log.Log.Infof("Create cluster completed for test %s", test.ID)
	case model.ExecStatusFailed, model.ExecStatusStopped:
		p.setTaskFailedFromRunning(currentTask)
		log.Log.Errorf(fmt.Errorf("task failed"), "Create cluster failed for test %s: %s", test.ID, taskBase.Message)
	}

	return test, nil
}
