package opsaction

import (
	"context"
	"fmt"
	"strconv"

	ccent "github.com/tidbcloud/devops/entity/cloudconfig"
	opent "github.com/tidbcloud/devops/entity/operation"
	cloudconfigapi "github.com/tidbcloud/devops/pkg/service/cloudconfig/api"
	"github.com/tidbcloud/devops/pkg/service/operation/actions"
)

var (
	ActionApplyConfigChangeViaConfigCenter opent.ActionMetadata = opent.ActionMetadata{
		Name: "ApplyConfigChangeViaConfigCenter",
		Type: opent.ActionTypeAuto,
	}
)

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

func NewApplyConfigChangeViaConfigCenter(comps *actions.Components) actions.AutoAction {
	return &applyConfigChangeViaConfigCenter{comps: comps}
}

func (a *applyConfigChangeViaConfigCenter) Metadata() opent.ActionMetadata {
	return ActionApplyConfigChangeViaConfigCenter
}

func (a *applyConfigChangeViaConfigCenter) PreCheck(ctx context.Context, instance *opent.Instance) error {
	// just check the cluster existence
	cid, err := actions.GetClusterID(instance)
	if err != nil {
		return err
	}

	changeID, skip, err := a.getParam(ctx, instance)
	if err != nil {
		return err
	}
	if skip {
		return nil
	}
	// if cluster is 3-layer, check the config change
	// check whether cluster id in the change is same as this action
	configChange, err := a.comps.CloudConfigAPI.GetConfigurationChange(ctx, changeID)
	if err != nil {
		return err
	}

	if configChange == nil {
		return fmt.Errorf("this config change %d is empty", changeID)
	}

	if configChange.TargetType != ccent.ConfigurationChangeTargetTypeCluster ||
		configChange.TargetCluster == nil {
		return fmt.Errorf("this config change %d does not belong to cluster %d", changeID, cid)
	}
	switch string(configChange.TargetCluster.ClusterType) {
	case string(ccent.ClusterTypeDedicatedTiDB):
		if configChange.TargetCluster.ClusterID != strconv.FormatUint(cid, 10) {
			return fmt.Errorf("this config change %d does not belong to cluster %d", changeID, cid)
		}
	case string(ccent.ClusterTypeTiCDC):
		ticdcClusterID, err := a.getCDCClusterIDFromParam(ctx, instance)
		if err != nil {
			return err
		}
		if configChange.TargetCluster.ClusterID != ticdcClusterID {
			return fmt.Errorf("this config change %d does not belong to cdc cluster %s", changeID, ticdcClusterID)
		}
	default:
		return fmt.Errorf("unsupported cluster type %s in the config change %d", configChange.TargetCluster.ClusterType, changeID)
	}

	// check the apply policy of the config change
	if configChange.ApplyPolicy != ccent.ParameterApplyPolicyEnsureUpdateToTC {
		return fmt.Errorf("the apply policy should be `%s` when use shutdown operation", ccent.ParameterApplyPolicyEnsureUpdateToTC)
	}
	return nil
}

func (a *applyConfigChangeViaConfigCenter) Start(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	changeID, skip, err := a.getParam(ctx, instance)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}

	if skip {
		return actions.NewActionResult(opent.ActionStatusSkipped, "skipped because the cluster is cloud-native architecture", nil), nil
	}
	// check status of the config change
	configChange, err := a.comps.CloudConfigAPI.GetConfigurationChange(ctx, changeID)
	if err != nil {
		return actions.RunningActionResult(fmt.Sprintf("failed to get config change: %v", err), nil), nil
	}

	if configChange.Phase != ccent.ConfigurationChangePhaseWaitForApply {
		return actions.NewActionResult(opent.ActionStatusCreated, fmt.Sprintf("the status of config change %d is %s", changeID, configChange.Phase), nil), nil
	}

	err = a.comps.CloudConfigAPI.ApplyConfigurationChange(ctx, changeID, cloudconfigapi.ApplyConfigurationChangeParam{
		// Bypassing verification, because it is already checked in method StartInstanceAction
		OperatorEmail: instance.Applicant,
	})
	if err != nil {
		return nil, err
	}

	return actions.RunningActionResult("sent request to apply config change", nil), nil
}

func (a *applyConfigChangeViaConfigCenter) Result(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	changeID, skip, err := a.getParam(ctx, instance)
	if err != nil {
		return nil, err
	}
	if skip {
		return actions.NewActionResult(opent.ActionStatusSkipped, "skipped because the cluster is cloud-native architecture", nil), nil
	}

	// check status of the config change
	configChange, err := a.comps.CloudConfigAPI.GetConfigurationChange(ctx, changeID)
	if err != nil {
		return actions.RunningActionResult(fmt.Sprintf("failed to get config change: %v", err), nil), nil
	}

	if configChange.Phase != ccent.ConfigurationChangePhaseApplied {
		return actions.RunningActionResult(fmt.Sprintf("the status of config change %d is %s", changeID, configChange.Phase), nil), nil
	}

	return actions.SucceededActionResult(nil), nil
}

func (a *applyConfigChangeViaConfigCenter) getParam(ctx context.Context, instance *opent.Instance) (uint64, bool, error) {
	skip := true
	cid, err := actions.GetClusterID(instance)
	if err != nil {
		return 0, skip, err
	}
	changeID, exist := instance.GetParam(opent.ParamConfigChangeID)
	if !exist || len(changeID) == 0 {
		return 0, skip, fmt.Errorf("param change_id is required")
	}
	id, err := strconv.ParseUint(changeID, 10, 64)
	if err != nil {
		return 0, skip, fmt.Errorf("parse change_id failed: %w", err)
	}

	cluster, err := a.comps.CoreSvc.GetCluster(ctx, cid, false)
	if err != nil {
		return 0, skip, err
	}

	newArch := false
	if cluster.Attributes != nil && cluster.Attributes.UseNewDedicatedArch != nil {
		newArch = *cluster.Attributes.UseNewDedicatedArch
	}
	if !newArch {
		return 0, skip, nil
	}

	// if cluster type is 3-layer, get change id
	skip = false
	return id, skip, nil
}

func (a *applyConfigChangeViaConfigCenter) getCDCClusterIDFromParam(ctx context.Context, instance *opent.Instance) (string, error) {
	ticdcClusterID, ok := instance.GetParam(opent.ParamCDCClusterID)
	if !ok {
		return "", fmt.Errorf("cdc cluster id param is not found")
	}
	return ticdcClusterID, nil
}
