package resourcepool

import (
	"context"

	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/resourcepool"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

// setDefaultPoolIfMissing will set the default resource pool for the org in the region to the given resourcePoolId
// only if there is no existing strategy
func (s *serviceImpl) setDefaultPoolIfMissing(ctx context.Context,
	cloudRegion *entity.Region,
	orgId uint64,
	resourcePoolId uint64,
	servicePlan entity.ServicePlan) (*entity.ResourcePoolStrategy, bizerr.BizError) {
	strategy, err := s.tryGetStrategy(ctx, cloudRegion, orgId, servicePlan)
	if err != nil {
		return nil, err
	}

	if strategy != nil {
		return strategy, nil
	}
	return s.createStrategy(ctx, cloudRegion, orgId, resourcePoolId, servicePlan)
}

// upsertDefaultResourcePool will set the default resource pool for the org in the region to the given resourcePoolId
// if there is no strategy, create a new one
// if there is already a strategy, update the default resource pool id
func (s *serviceImpl) upsertDefaultResourcePool(ctx context.Context,
	cloudRegion *entity.Region,
	orgId uint64,
	resourcePoolId uint64,
	servicePlan entity.ServicePlan) (*entity.ResourcePoolStrategy, bizerr.BizError) {
	strategy, err := s.tryGetStrategy(ctx, cloudRegion, orgId, servicePlan)
	if err != nil {
		return nil, err
	}

	if strategy == nil {
		return s.createStrategy(ctx, cloudRegion, orgId, resourcePoolId, servicePlan)
	}

	if strategy.DefaultResourcePoolId == resourcePoolId {
		return strategy, nil
	}

	strategy.DefaultResourcePoolId = resourcePoolId
	saveErr := s.resourcePoolStrategyRepo.Save(ctx, strategy)
	if saveErr != nil {
		return strategy, saveErr
	}
	return strategy, nil
}

// createStrategy creates a new ResourcePoolStrategy for the org in the region
func (s *serviceImpl) createStrategy(ctx context.Context,
	cloudRegion *entity.Region,
	orgId uint64,
	resourcePoolId uint64,
	servicePlan entity.ServicePlan) (*entity.ResourcePoolStrategy, bizerr.BizError) {
	strategy := &entity.ResourcePoolStrategy{
		ResourcePoolStrategy: &globalngv1.ResourcePoolStrategy{
			OrganizationId:        orgId,
			DefaultResourcePoolId: resourcePoolId,
			ProviderId:            cloudRegion.CloudProviderId,
			RegionId:              cloudRegion.Id,
			ServicePlan:           servicePlan,
		},
		RegionId:        cloudRegion.RegionID,
		CloudProviderId: cloudRegion.ProviderID,
	}

	err := s.resourcePoolStrategyRepo.Create(ctx, strategy)

	return strategy, err
}

// tryGetStrategy queries and returns the ResourcePoolStrategy for the org in the region.
// Returns nil if no strategy is found.
func (s *serviceImpl) tryGetStrategy(ctx context.Context,
	cloudRegion *entity.Region,
	orgId uint64,
	servicePlan entity.ServicePlan) (*entity.ResourcePoolStrategy, bizerr.BizError) {
	strategies, err := s.resourcePoolStrategyRepo.Query(ctx, &resourcepool.StrategyFilter{
		OrganizationIds:    []uint64{orgId},
		CloudProviderNames: []entity.CloudProviderName{cloudRegion.CloudProviderId},
		RegionNames:        []entity.RegionName{cloudRegion.Id},
		ServicePlans:       []entity.ServicePlan{servicePlan},
	})
	if err != nil {
		return nil, err
	}

	if len(strategies) == 0 {
		return nil, nil //nolint:nilnil // ignore nil check
	}

	return strategies[0], nil
}

func (s *serviceImpl) updateStrategyAfterPoolDeletion(ctx context.Context,
	cloudRegion *entity.Region,
	orgId uint64,
	deletedResourcePoolId uint64,
	servicePlan entity.ServicePlan) (*entity.ResourcePoolStrategy, bizerr.BizError) {
	strategy, err := s.tryGetStrategy(ctx, cloudRegion, orgId, servicePlan)
	if err != nil {
		return nil, err
	}
	// no strategy, nothing to do
	if strategy == nil {
		return nil, nil //nolint:nilnil // ignore nil check
	}
	// strategy exists but not pointing to the deleted resource pool, nothing to do
	if strategy.DefaultResourcePoolId != deletedResourcePoolId {
		return strategy, nil
	}

	// find another resource pool in the same region for the org
	resourcePools, _, err := s.resourcePoolRepo.Query(ctx, &resourcepool.Filter{
		CloudProviderNames: []entity.CloudProviderName{cloudRegion.CloudProviderId},
		RegionNames:        []entity.RegionName{cloudRegion.Id},
		ServicePlans:       []entity.ServicePlan{servicePlan},
		OwnerType:          int(globalngv1.ResourcePool_ORGANIZATION),
		OwnerIds:           []uint64{orgId},
	})
	if err != nil {
		return strategy, err
	}

	for _, resourcePool := range resourcePools {
		// pick the first one that is not the deleted one
		if resourcePool.Id != deletedResourcePoolId {
			strategy.DefaultResourcePoolId = resourcePool.Id
			saveErr := s.resourcePoolStrategyRepo.Save(ctx, strategy)
			if saveErr != nil {
				return strategy, saveErr
			}

			return strategy, nil
		}
	}

	// no other resource pool found, delete the strategy
	delErr := s.resourcePoolStrategyRepo.Delete(ctx, strategy.Id)
	if delErr != nil {
		return strategy, delErr
	}

	return strategy, nil
}

// deleteStrategiesByPoolId removes every ResourcePoolStrategy whose
// default_resource_pool_id equals the given poolId. Used when a shared
// (Essential_V2) pool is deleted — all org-default references are cleaned up.
func (s *serviceImpl) deleteStrategiesByPoolId(ctx context.Context, resourcePoolId uint64) bizerr.BizError {
	strategies, err := s.resourcePoolStrategyRepo.Query(ctx, &resourcepool.StrategyFilter{
		DefaultResourcePoolIds: []uint64{resourcePoolId},
	})
	if err != nil {
		return bizerr.WrapError(err, "query strategies by resource pool id failed")
	}
	for _, st := range strategies {
		if delErr := s.resourcePoolStrategyRepo.Delete(ctx, st.Id); delErr != nil {
			return delErr
		}
	}
	return nil
}
