package task

import (
	"context"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	k8sent "github.com/tidbcloud/devops/entity/k8supgrade"
	maintent "github.com/tidbcloud/devops/entity/maintenance"
	"github.com/tidbcloud/devops/pkg/infra/log"
	cmxapi "github.com/tidbcloud/devops/pkg/service/clustermaintenance/api"
	"github.com/tidbcloud/devops/pkg/service/k8supgrade/infrainfo"
	"github.com/tidbcloud/devops/pkg/service/k8supgrade/task/utils"
)

const (
	annotationRequestStepPrefix = "maintenance.pingcap.com/request-step-"
	annotationReleaseStepPrefix = "maintenance.pingcap.com/release-step-"
	maintenanceAnnotationDone   = "done"
)

type maintenanceAnnotation struct {
	Spec struct {
		ClusterID string `json:"cluster_id"`
		Time      string `json:"time"`
	} `json:"spec"`
	Status struct {
		Result string `json:"result"`
		Time   string `json:"time"`
	} `json:"status"`
}

func (s *svc) syncUOMaintenanceAnnotations(
	ctx context.Context,
	cluster *k8sent.K8sCluster,
	currentUO *k8sent.UpgradeOperation,
	uoName, uoNs string,
) error {
	if len(currentUO.Annotations) == 0 {
		return nil
	}

	logger := log.FromContext(ctx, "func", "syncUOMaintenanceAnnotations", "name", uoName, "namespace", uoNs, "cluster", cluster.Name)
	mw, err := s.maintenanceWindowSvc.GetMaintenance(ctx, *cluster.MaintenanceWindowID, cmxapi.GetMaintenanceParam{
		WithExecutions: true,
	})
	if err != nil {
		return fmt.Errorf("failed to get maintenance window: %w", err)
	}

	updated := false
	changedAnnotations := make(map[string]string)
	for key, raw := range currentUO.Annotations {
		changed, err := s.handleMaintenanceAnnotation(ctx, key, raw, mw, currentUO.Annotations)
		if err != nil {
			logger.Error(err, "failed to handle maintenance annotation", "key", key)
			continue
		}
		if changed {
			updated = true
			changedAnnotations[key] = currentUO.Annotations[key]
			logger.Info("handled maintenance annotation successfully", "key", key)
		}
	}
	if !updated {
		return nil
	}

	if err := s.infraInfoSvc.PatchUpgradeOperationAnnotations(ctx, &infrainfo.PatchUpgradeOperationAnnotationsReq{
		Name:          uoName,
		Namespace:     uoNs,
		Annotations:   changedAnnotations,
		CloudProvider: cluster.Info.CloudProvider,
		Region:        cluster.Info.Region,
	}); err != nil {
		return fmt.Errorf("failed to patch maintenance annotations: %w", err)
	}
	logger.Info("patched maintenance annotations", "name", uoName, "changedAnnotations", changedAnnotations)
	return nil
}

func (s *svc) handleMaintenanceAnnotation(ctx context.Context, key, raw string, mw *maintent.ClusterMaintenance, annotations map[string]string) (bool, error) {
	kind, ok := maintenanceAnnotationKind(key)
	if !ok {
		return false, nil
	}

	var ann maintenanceAnnotation
	if err := json.Unmarshal([]byte(raw), &ann); err != nil {
		return false, fmt.Errorf("failed to parse annotation %q: %w", key, err)
	}
	if ann.Spec.ClusterID == "" || ann.Status.Result == maintenanceAnnotationDone {
		return false, nil
	}

	clusterID, err := strconv.ParseUint(ann.Spec.ClusterID, 10, 64)
	if err != nil {
		return false, fmt.Errorf("invalid cluster_id in annotation %q: %w", key, err)
	}

	switch kind {
	case "request":
		if err := utils.StartMaintenanceExecutionForCluster(ctx, s.maintenanceWindowSvc, mw, clusterID); err != nil {
			return false, err
		}
	case "release":
		if err := utils.FinishMaintenanceExecutionForCluster(ctx, s.maintenanceWindowSvc, mw, clusterID); err != nil {
			return false, err
		}
	default:
		return false, nil
	}

	ann.Status.Result = maintenanceAnnotationDone
	ann.Status.Time = strconv.FormatInt(time.Now().Unix(), 10)
	encoded, err := json.Marshal(ann)
	if err != nil {
		return false, err
	}
	annotations[key] = string(encoded)
	return true, nil
}

func maintenanceAnnotationKind(key string) (string, bool) {
	switch {
	case strings.HasPrefix(key, annotationRequestStepPrefix):
		return "request", true
	case strings.HasPrefix(key, annotationReleaseStepPrefix):
		return "release", true
	default:
		return "", false
	}
}
