package common

import (
	"context"
	"fmt"
	"time"

	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/yaml"

	"github.com/tidbcloud/eks-provider/controllers/cluster/task"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
)

const (
	workflowRequeueInterval = 10 * time.Second
)

// Workflow manages workflow resources based on cluster annotations.
// It creates workflows from annotations and removes annotations when workflows reach terminal states.
type Workflow struct {
	*task.BaseTask
}

func NewWorkflow(ctx *task.Context) *Workflow {
	return &Workflow{BaseTask: &task.BaseTask{Context: ctx}}
}

func (t *Workflow) Name() string {
	return "Workflow"
}

// Create processes workflow annotations: creates workflows from annotations and removes
// annotations for workflows in terminal states. Returns an error if creation fails or workflows are still running.
func (t *Workflow) Create(ctx context.Context) deploy.Result {
	if err := t.createWorkflowsFromAnnotations(ctx); err != nil {
		return deploy.Error(err)
	}
	if err := t.removeTerminalWorkflowAnnotations(ctx); err != nil {
		return deploy.Error(err).WithRequeuAfter(workflowRequeueInterval)
	}
	return deploy.Done()
}

func (t *Workflow) Delete(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *Workflow) createWorkflowsFromAnnotations(ctx context.Context) error {
	if t.Cluster.Annotations == nil {
		return nil
	}

	for _, annotationKey := range t.getWorkflowAnnotationKeys() {
		if err := t.createWorkflowFromAnnotation(ctx, annotationKey); err != nil {
			return err
		}
	}
	return nil
}

func (t *Workflow) createWorkflowFromAnnotation(ctx context.Context, key string) error {
	value, ok := t.Cluster.Annotations[key]
	if !ok || value == "" {
		return nil
	}

	var wf cloudv1.Workflow
	if err := yaml.Unmarshal([]byte(value), &wf); err != nil {
		t.Logger.Error(err, "failed to unmarshal workflow from annotation", "annotation", key)
		return fmt.Errorf("failed to unmarshal workflow from annotation %s: %w", key, err)
	}

	if _, err := k8s.CreateIgnoreExists(ctx, t.Local, &wf); err != nil {
		t.Logger.Error(err, "failed to create workflow", "annotation", key, "workflow", client.ObjectKeyFromObject(&wf))
		return fmt.Errorf("failed to create workflow from annotation %s: %w", key, err)
	}
	return nil
}

func (t *Workflow) getWorkflowAnnotationKeys() []string {
	return []string{
		consts.AnnoKeyScaleClusterWorkflow,
		consts.AnnoKeyTiProxyToggleWorkflow,
	}
}

// removeTerminalWorkflowAnnotations removes annotations for workflows in terminal states or deleted workflows.
// Returns an error if any workflow is still running.
func (t *Workflow) removeTerminalWorkflowAnnotations(ctx context.Context) error {
	if t.Cluster.Annotations == nil {
		return nil
	}

	hasPendingWorkflows := false
	for _, annotationKey := range t.getWorkflowAnnotationKeys() {
		annotationValue, ok := t.Cluster.Annotations[annotationKey]
		if !ok {
			continue
		}

		wf := &cloudv1.Workflow{}
		if err := yaml.Unmarshal([]byte(annotationValue), &wf); err != nil {
			t.Logger.Error(err, "failed to unmarshal workflow from annotation", "annotation", annotationKey, "value", annotationValue)
			return fmt.Errorf("failed to unmarshal workflow from annotation %s: %w", annotationKey, err)
		}

		wfKey := client.ObjectKey{Namespace: wf.Namespace, Name: wf.Name}
		existingWf := &cloudv1.Workflow{}
		if err := t.Local.Get(ctx, wfKey, existingWf); err != nil {
			if apierrors.IsNotFound(err) {
				t.Logger.Info("workflow not found, removing annotation", "annotation", annotationKey, "workflow", wfKey)
				delete(t.Cluster.Annotations, annotationKey)
				continue
			}
			t.Logger.Error(err, "failed to get workflow", "annotation", annotationKey, "workflow", wfKey)
			return fmt.Errorf("failed to get workflow %s: %w", wfKey, err)
		}

		if !existingWf.Done() {
			t.Logger.V(1).Info("workflow is not in terminal state, skipping annotation removal",
				"annotation", annotationKey, "phase", existingWf.Status.Phase, "workflow", wfKey)
			hasPendingWorkflows = true
			continue
		}

		t.Logger.Info("workflow is in terminal state, removing annotation",
			"annotation", annotationKey, "phase", existingWf.Status.Phase, "workflow", wfKey)
		delete(t.Cluster.Annotations, annotationKey)
	}

	if hasPendingWorkflows {
		return fmt.Errorf("some workflows are still running, retry needed")
	}
	return nil
}
