package gkeheterogeneous

import (
	"context"
	"slices"

	regionaldedicatedv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/regionserver"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	clusterrepo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/cluster"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/region"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/featuregate"
)

func PreviewApply(
	ctx context.Context,
	clusterRepo clusterrepo.Repo,
	regionRepo region.Repo,
	featGateCli featuregate.Service,
	regionServerCli regionserver.Client,
	req *globaldedicatedv1.ApplyGKEHeterogeneousDeployRequest,
) (*globaldedicatedv1.GKEHeterogeneousDeploy, bizerr.BizError) {
	_, regionEn, berr := Prepare(
		ctx,
		clusterRepo,
		regionRepo,
		featGateCli,
		req.GetTenantId(),
		req.GetProjectId(),
		req.GetClusterId(),
		req.GetComponentType(),
		true,
		true,
	)
	if berr != nil {
		return nil, berr
	}
	if berr := validateApplyRequest(req); berr != nil {
		return nil, berr
	}
	regionalComponent, berr := ToRegionalComponentTypeOrError(req.GetComponentType())
	if berr != nil {
		return nil, berr
	}

	clusterID := optionalUint64String(req.GetClusterId())
	tidbGroupID := optionalUint64String(req.GetTidbGroupId())
	deploy, deployExists, berr := GetDeploy(ctx, regionServerCli, regionEn, clusterID, regionalComponent, tidbGroupID)
	if berr != nil {
		return nil, berr
	}
	if !deployExists {
		return &globaldedicatedv1.GKEHeterogeneousDeploy{
			Enabled:                 true,
			ComponentType:           req.GetComponentType(),
			CurrentInstanceType:     req.GetCurrentInstanceType(),
			AlternativeInstanceType: req.GetAlternativeInstanceType(),
			TidbGroupId:             req.GetTidbGroupId(),
		}, nil
	}
	if berr := ValidateInstanceTypes(deploy, req.GetCurrentInstanceType(), req.GetAlternativeInstanceType()); berr != nil {
		return nil, berr
	}
	_, berr = FindExistingDeployNodePoolTarget(
		ctx,
		regionServerCli,
		regionEn,
		clusterID,
		regionalComponent,
		tidbGroupID,
		deploy,
		req.GetAlternativeInstanceType(),
	)
	if berr != nil {
		return nil, berr
	}
	return &globaldedicatedv1.GKEHeterogeneousDeploy{
		Enabled:                 deploy.GetEnabled(),
		ComponentType:           req.GetComponentType(),
		CurrentInstanceType:     req.GetCurrentInstanceType(),
		AlternativeInstanceType: req.GetAlternativeInstanceType(),
		TidbGroupId:             req.GetTidbGroupId(),
	}, nil
}

func ApplyWithNodePoolTarget(
	ctx context.Context,
	clusterRepo clusterrepo.Repo,
	regionRepo region.Repo,
	regionServerCli regionserver.Client,
	req *globaldedicatedv1.ApplyGKEHeterogeneousDeployRequest,
) bizerr.BizError {
	_, regionEn, berr := Prepare(
		ctx,
		clusterRepo,
		regionRepo,
		nil,
		req.GetTenantId(),
		req.GetProjectId(),
		req.GetClusterId(),
		req.GetComponentType(),
		true,
		false,
	)
	if berr != nil {
		return berr
	}
	if berr := validateApplyRequest(req); berr != nil {
		return berr
	}
	regionalComponent, berr := ToRegionalComponentTypeOrError(req.GetComponentType())
	if berr != nil {
		return berr
	}
	clusterID := optionalUint64String(req.GetClusterId())
	tidbGroupID := optionalUint64String(req.GetTidbGroupId())

	deploy, deployExists, berr := GetDeploy(ctx, regionServerCli, regionEn, clusterID, regionalComponent, tidbGroupID)
	if berr != nil {
		return berr
	}
	if deployExists {
		return validateExistingApplyTarget(
			ctx,
			regionServerCli,
			regionEn,
			clusterID,
			regionalComponent,
			tidbGroupID,
			deploy,
			req,
		)
	}

	baseNodePool, berr := EnsureSharedNodePool(
		ctx,
		regionServerCli,
		regionEn,
		clusterID,
		"",
		req.GetCurrentInstanceType(),
	)
	if berr != nil {
		return berr
	}

	alternativeNodePool, berr := EnsureSharedNodePool(
		ctx,
		regionServerCli,
		regionEn,
		clusterID,
		"",
		req.GetAlternativeInstanceType(),
	)
	if berr != nil {
		return berr
	}

	bindings, berr := NodePoolBindings(
		baseNodePool,
		alternativeNodePool,
		req.GetCurrentInstanceType(),
		req.GetAlternativeInstanceType(),
	)
	if berr != nil {
		return berr
	}
	computeClass, berr := regionServerCli.CreateGKEHeterogeneousComputeClass(
		ctx,
		regionEn,
		newCreateComputeClassRequest(clusterID, regionalComponent, bindings),
	)
	if berr != nil && berr.ErrCode() == bizerr.ErrCodeRecordExist {
		resp, gerr := regionServerCli.GetGKEHeterogeneousComputeClass(
			ctx,
			regionEn,
			newGetComputeClassRequest(clusterID,
				bindings.InstanceTypes[0], bindings.InstanceTypes[1]),
		)
		if gerr != nil {
			return gerr
		}
		classes := resp.GetComputeClasses()
		if len(classes) == 0 {
			return bizerr.New(bizerr.ErrCodeRecordNotFound,
				"compute class not found for base_instance_type=%s heterogeneous_instance_type=%s",
				bindings.InstanceTypes[0], bindings.InstanceTypes[1])
		}
		for _, class := range classes {
			if slices.Equal(class.GetNodePoolNames(), bindings.NodePoolNames) &&
				slices.Equal(class.GetInstanceTypes(), bindings.InstanceTypes) {
				computeClass = class
				break
			}
		}
		if computeClass == nil {
			return bizerr.New(bizerr.ErrCodeRegionalAPI,
				"no compute class matches requested binding node_pool_names=%v instance_types=%v",
				bindings.NodePoolNames, bindings.InstanceTypes)
		}
		berr = nil
	}
	if berr != nil {
		return berr
	}
	if computeClass.GetName() == "" {
		return bizerr.New(bizerr.ErrCodeRegionalAPI, "empty compute class name from regional-server")
	}
	_, berr = regionServerCli.CreateGKEHeterogeneousDeploy(ctx, regionEn, &regionaldedicatedv1.CreateGKEHeterogeneousDeployRequest{
		ClusterId:        clusterID,
		ComponentType:    regionalComponent,
		NodePoolNames:    bindings.NodePoolNames,
		InstanceTypes:    bindings.InstanceTypes[1:],
		TidbGroupId:      tidbGroupID,
		ComputeClassName: computeClass.GetName(),
	})
	if berr != nil && berr.ErrCode() == bizerr.ErrCodeRecordExist {
		deploy, deployExists, getErr := GetDeploy(ctx, regionServerCli, regionEn, clusterID, regionalComponent, tidbGroupID)
		if getErr != nil {
			return getErr
		}
		if !deployExists {
			return bizerr.New(bizerr.ErrCodeRegionalAPI, "gke heterogeneous deploy already exists but cannot be read")
		}
		return validateExistingApplyTargetMatchesBindings(
			ctx,
			regionServerCli,
			regionEn,
			clusterID,
			regionalComponent,
			tidbGroupID,
			deploy,
			req,
			bindings,
			computeClass.GetName(),
		)
	}
	return berr
}

func validateExistingApplyTargetMatchesBindings(
	ctx context.Context,
	regionServerCli regionserver.Client,
	regionEn *entity.Region,
	clusterID string,
	regionalComponent regionaldedicatedv1.ComponentType,
	tidbGroupID string,
	deploy *regionaldedicatedv1.GKEHeterogeneousDeploy,
	req *globaldedicatedv1.ApplyGKEHeterogeneousDeployRequest,
	bindings *NodePoolBindingSet,
	computeClassName string,
) bizerr.BizError {
	if bindings == nil {
		return bizerr.New(bizerr.ErrCodeRegionalAPI, "empty gke heterogeneous node pool bindings")
	}
	if deploy == nil || !deploy.GetEnabled() {
		return bizerr.New(bizerr.ErrCodeRegionalAPI, "existing gke heterogeneous deploy is not enabled")
	}
	if deploy.GetComputeClassName() != computeClassName {
		return bizerr.New(
			bizerr.ErrCodeRegionalAPI,
			"existing gke heterogeneous deploy compute class mismatch: got %s, want %s",
			deploy.GetComputeClassName(),
			computeClassName,
		)
	}
	if !slices.Equal(deploy.GetNodePoolNames(), bindings.NodePoolNames) {
		return bizerr.New(
			bizerr.ErrCodeRegionalAPI,
			"existing gke heterogeneous deploy node pools mismatch: got %v, want %v",
			deploy.GetNodePoolNames(),
			bindings.NodePoolNames,
		)
	}
	if !slices.Equal(deploy.GetInstanceTypes(), bindings.InstanceTypes) {
		return bizerr.New(
			bizerr.ErrCodeRegionalAPI,
			"existing gke heterogeneous deploy instance types mismatch: got %v, want %v",
			deploy.GetInstanceTypes(),
			bindings.InstanceTypes,
		)
	}
	return validateExistingApplyTarget(
		ctx,
		regionServerCli,
		regionEn,
		clusterID,
		regionalComponent,
		tidbGroupID,
		deploy,
		req,
	)
}

func validateExistingApplyTarget(
	ctx context.Context,
	regionServerCli regionserver.Client,
	regionEn *entity.Region,
	clusterID string,
	regionalComponent regionaldedicatedv1.ComponentType,
	tidbGroupID string,
	deploy *regionaldedicatedv1.GKEHeterogeneousDeploy,
	req *globaldedicatedv1.ApplyGKEHeterogeneousDeployRequest,
) bizerr.BizError {
	if berr := ValidateInstanceTypes(deploy, req.GetCurrentInstanceType(), req.GetAlternativeInstanceType()); berr != nil {
		return berr
	}
	nodePool, berr := FindExistingDeployNodePoolTarget(
		ctx,
		regionServerCli,
		regionEn,
		clusterID,
		regionalComponent,
		tidbGroupID,
		deploy,
		req.GetAlternativeInstanceType(),
	)
	if berr != nil {
		return berr
	}
	if nodePool == nil || nodePool.GetNodePoolName() == "" {
		return bizerr.New(bizerr.ErrCodeRegionalAPI, "empty gke heterogeneous node pool from regional-server")
	}
	return nil
}

func Get(
	ctx context.Context,
	clusterRepo clusterrepo.Repo,
	regionRepo region.Repo,
	regionServerCli regionserver.Client,
	req *globaldedicatedv1.GetGKEHeterogeneousDeployRequest,
) (*globaldedicatedv1.GKEHeterogeneousDeploy, bizerr.BizError) {
	if berr := validateTidbGroupForComponent(req.GetComponentType(), req.GetTidbGroupId()); berr != nil {
		return nil, berr
	}
	cluster, regionEn, berr := Prepare(
		ctx,
		clusterRepo,
		regionRepo,
		nil,
		req.GetTenantId(),
		req.GetProjectId(),
		req.GetClusterId(),
		req.GetComponentType(),
		false,
		false,
	)
	if berr != nil {
		return nil, berr
	}
	clusterID := optionalUint64String(cluster.ClusterId)
	tidbGroupID := optionalUint64String(req.GetTidbGroupId())
	regionalComponent, berr := ToRegionalComponentTypeOrError(req.GetComponentType())
	if berr != nil {
		return nil, berr
	}
	deploy, deployExists, berr := GetDeploy(ctx, regionServerCli, regionEn, clusterID, regionalComponent, tidbGroupID)
	if berr != nil {
		return nil, berr
	}
	if !deployExists {
		return &globaldedicatedv1.GKEHeterogeneousDeploy{
			Enabled:       false,
			ComponentType: req.GetComponentType(),
			TidbGroupId:   req.GetTidbGroupId(),
		}, nil
	}
	instanceTypes := deploy.GetInstanceTypes()
	var currentInstanceType, alternativeInstanceType string
	if len(instanceTypes) > 0 {
		currentInstanceType = instanceTypes[0]
	}
	if len(instanceTypes) > 1 {
		alternativeInstanceType = instanceTypes[len(instanceTypes)-1]
	}
	nodePool, berr := FindNodePool(ctx, regionServerCli, regionEn, clusterID, regionalComponent, tidbGroupID, deploy, alternativeInstanceType)
	if berr != nil {
		if deploy.GetStatus() != regionaldedicatedv1.GKEHeterogeneousDeployStatus_GKE_HETEROGENEOUS_DEPLOY_STATUS_INTENT_ENABLED_NOT_READY {
			return nil, berr
		}
	}
	return ToGlobalDeploy(deploy, nodePool, currentInstanceType, alternativeInstanceType)
}

func Delete(
	ctx context.Context,
	clusterRepo clusterrepo.Repo,
	regionRepo region.Repo,
	regionServerCli regionserver.Client,
	req *globaldedicatedv1.DeleteGKEHeterogeneousDeployRequest,
) bizerr.BizError {
	if berr := validateTidbGroupForComponent(req.GetComponentType(), req.GetTidbGroupId()); berr != nil {
		return berr
	}
	cluster, regionEn, berr := Prepare(
		ctx,
		clusterRepo,
		regionRepo,
		nil,
		req.GetTenantId(),
		req.GetProjectId(),
		req.GetClusterId(),
		req.GetComponentType(),
		true,
		false,
	)
	if berr != nil {
		return berr
	}
	clusterID := optionalUint64String(cluster.ClusterId)
	tidbGroupID := optionalUint64String(req.GetTidbGroupId())
	regionalComponent, berr := ToRegionalComponentTypeOrError(req.GetComponentType())
	if berr != nil {
		return berr
	}

	assign, getErr := regionServerCli.GetGKEHeterogeneousGatekeeperAssign(
		ctx,
		regionEn,
		&regionaldedicatedv1.GetGKEHeterogeneousGatekeeperAssignRequest{
			ClusterId:     clusterID,
			ComponentType: regionalComponent,
			TidbGroupId:   tidbGroupID,
		},
	)
	if getErr != nil && getErr.ErrCode() != bizerr.ErrCodeRecordNotFound {
		return getErr
	}
	if assign != nil && assign.GetName() != "" {
		if err := regionServerCli.DeleteGKEHeterogeneousGatekeeperAssign(
			ctx,
			regionEn,
			&regionaldedicatedv1.DeleteGKEHeterogeneousGatekeeperAssignRequest{
				ClusterId: clusterID,
				Name:      assign.GetName(),
			},
		); err != nil && err.ErrCode() != bizerr.ErrCodeRecordNotFound {
			return err
		}
	}

	if err := regionServerCli.DeleteGKEHeterogeneousDeploy(ctx, regionEn, &regionaldedicatedv1.DeleteGKEHeterogeneousDeployRequest{
		ClusterId:     clusterID,
		ComponentType: regionalComponent,
		TidbGroupId:   tidbGroupID,
	}); err != nil && err.ErrCode() != bizerr.ErrCodeRecordNotFound {
		return err
	}
	return nil
}
