package regionaction

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	apiv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-server/v1"
	regionalv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"

	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/pkg/service/operation/actions"
)

const (
	defaultTTL = "168h" // 7 days
)

type upgradeBase struct {
	actions.BaseAction
	comps *actions.Components
}

func (b *upgradeBase) Start(ctx context.Context, instance *opent.Instance, buildRequest func(*opent.Instance, *regionalv1.K8SInformation) (*regionalv1.CreateUpgradeOperationRequest, error)) (*actions.ActionResult, error) {
	cidStr, _ := instance.GetParam(opent.ParamClusterID)
	if cidStr == "" {
		return actions.FailedActionResult(fmt.Errorf("cluster_id is required")), nil
	}
	cid, err := strconv.ParseUint(cidStr, 10, 64)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}

	info, err := getK8sInformation(ctx, b.comps, cid, cidStr)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}
	b.comps.Logger.Info("get k8s information info", "info", info)

	req, err := buildRequest(instance, info)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}
	output := map[opent.OutputName]string{
		opent.OutputOperationID:   req.UpgradeOperation.Name,
		opent.OutputRegion:        info.Region,
		opent.OutputCloudProvider: info.CloudProvider,
	}
	if err = b.comps.MgmtHTTP.RegionServer(info.Region, info.CloudProvider).CreateUpgradeOperation(ctx, req); err != nil {
		return actions.FailedActionResult(err), nil
	}

	return actions.RunningActionResult("sent request to create the UpgradeOperation", output), nil
}

func (b *upgradeBase) Result(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	region, _ := instance.GetOutput(opent.OutputRegion)
	cloudProvider, _ := instance.GetOutput(opent.OutputCloudProvider)
	uoName, _ := instance.GetOutput(opent.OutputOperationID)

	uo, err := b.comps.MgmtHTTP.RegionServer(region, cloudProvider).GetUpgradeOperation(ctx, &regionalv1.GetUpgradeOperationRequest{
		Name: uoName,
	})
	if err != nil {
		return actions.NewActionResult(opent.ActionStatusUnknown, err.Error(), nil), nil
	}
	if uo == nil || uo.Status == nil {
		return actions.NewActionResult(opent.ActionStatusUnknown, "UpgradeOperation is not found", instance.Outputs), nil
	}

	switch cloudv1.UpgradeOperationPhase(uo.Status.Phase) {
	case cloudv1.UpgradeOperationPhaseNone, cloudv1.UpgradeOperationPhasePending:
		return actions.RunningActionResult("UpgradeOperation is pending", nil), nil
	case cloudv1.UpgradeOperationPhaseRunning:
		return actions.RunningActionResult(fmt.Sprintf("UpgradeOperation is running: %s", uo.Status.Reason), nil), nil
	case cloudv1.UpgradeOperationPhaseCompleted:
		return actions.SucceededActionResult(nil), nil
	case cloudv1.UpgradeOperationPhaseFailed:
		return actions.FailedActionResult(fmt.Errorf("UpgradeOperation %s is failed: %s", uo.Name, uo.Status.Reason)), nil
	default:
		return actions.NewActionResult(opent.ActionStatusUnknown, "Unknown UpgradeOperation phase", nil), nil
	}
}

func getK8sInformation(ctx context.Context, comps *actions.Components, cid uint64, cidStr string) (*regionalv1.K8SInformation, error) {
	cluster1, err := comps.CoreSvc.GetCluster(ctx, cid, false)
	if err != nil {
		return nil, fmt.Errorf("failed to get cluster from core-svc: %w", err)
	}
	region, cloudProvider := strings.ToLower(cluster1.RegionName), strings.ToLower(cluster1.CloudProviderName)

	cluster2, err := comps.MgmtHTTP.ClusterServer(cidStr).GetCluster(ctx, &apiv1.GetClusterRequest{
		ClusterId: cidStr,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to get cluster from cluster-server: %w", err)
	}

	info, err := comps.MgmtHTTP.RegionServer(region, cloudProvider).GetK8sInformation(ctx, &regionalv1.GetK8SInformationRequest{
		Name: cluster2.Kubernetes.GetName(),
	})
	if err != nil {
		return nil, fmt.Errorf("failed to get k8s information: %w", err)
	}
	return info, nil
}
