package resourcepool

import (
	"context"

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

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

func (s *serviceImpl) PickSharedResourcePool(ctx context.Context,
	pool entity.ResourcePool, orgId uint64) (*entity.ResourcePool, bizerr.BizError) {
	if pool.ProviderId == globalcommonv1.CloudProvider_NAME_UNSPECIFIED {
		return nil, bizerr.New(bizerr.ErrCodeParams, "ProviderId must be set")
	}
	if pool.GetRegionId() == globalcommonv1.Region_NAME_UNSPECIFIED {
		return nil, bizerr.New(bizerr.ErrCodeParams, "RegionId must be set")
	}
	if pool.ServicePlan == globalcommonv1.ServicePlan_SERVICE_PLAN_UNSPECIFIED {
		return nil, bizerr.New(bizerr.ErrCodeParams, "ServicePlan must be set")
	}
	// 1. Try org default strategy (orgId > 0 only).
	if orgId > 0 {
		if rp, berr := s.pickSharedPoolByStrategy(ctx, pool, []uint64{orgId}); berr != nil {
			return nil, berr
		} else if rp != nil {
			return rp, nil
		}
	}

	// 2. Try global default strategy (organization_id = 0).
	if rp, berr := s.pickSharedPoolByStrategy(ctx, pool, []uint64{0}); berr != nil {
		return nil, berr
	} else if rp != nil {
		return rp, nil
	}

	selectedRegion, err := s.regionRepo.SelectRegion(ctx, regionrepo.Filter{
		CloudProvider: pool.GetProviderId(),
		RegionId:      pool.GetRegionId(),
		TenantId:      0,
		ServicePlan:   pool.GetServicePlan(),
	})
	if err != nil {
		return nil, err
	}

	// 3. Fallback: return the first shared pool found (original behaviour).
	filter := &resourcepool.Filter{
		OwnerType:    int(globalngv1.ResourcePool_SHARED),
		OwnerIds:     []uint64{0},
		RegionId:     selectedRegion.RegionID,
		ServicePlans: []entity.ServicePlan{pool.ServicePlan},
		PageSize:     1,
	}
	if pool.GetServicePlan() == globalcommonv1.ServicePlan_Essential_V2 {
		filter.HighAvailabilityTypes = compatibleSharedPoolHighAvailabilityTypes(pool.GetHighAvailabilityType())
	}
	resourcePools, _, err := s.resourcePoolRepo.Query(ctx, filter)
	if err != nil {
		return nil, bizerr.WrapError(err, "query shared resource pool failed")
	}
	if len(resourcePools) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "shared resource pool not found")
	}

	return resourcePools[0], nil
}

// pickSharedPoolByStrategy looks up a ResourcePoolStrategy for the given org IDs
// and returns the referenced shared pool. Returns (nil, nil) when no usable
// strategy/pool is found so the caller can fall through to the next tier.
func (s *serviceImpl) pickSharedPoolByStrategy(
	ctx context.Context,
	pool entity.ResourcePool,
	orgIds []uint64,
) (*entity.ResourcePool, bizerr.BizError) {
	strategies, err := s.resourcePoolStrategyRepo.Query(ctx, &resourcepool.StrategyFilter{
		OrganizationIds:    orgIds,
		CloudProviderNames: []entity.CloudProviderName{pool.GetProviderId()},
		RegionNames:        []entity.RegionName{pool.GetRegionId()},
		ServicePlans:       []entity.ServicePlan{pool.ServicePlan},
	})
	if err != nil {
		return nil, bizerr.WrapError(err, "query resource pool strategy failed")
	}
	if len(strategies) == 0 {
		return nil, nil //nolint:nilnil // no strategy found, fall through
	}

	rp, loadErr := s.resourcePoolRepo.Load(ctx, strategies[0].DefaultResourcePoolId, false)
	if loadErr != nil {
		tilog.Warn(ctx, "strategy references unavailable resource pool, falling through",
			zap.Uint64("strategyId", strategies[0].Id),
			zap.Uint64("resourcePoolId", strategies[0].DefaultResourcePoolId),
			zap.Error(loadErr))
		return nil, nil //nolint:nilnil // pool unavailable, fall through
	}
	if rp == nil {
		return nil, nil //nolint:nilnil // pool nil, fall through
	}
	if pool.GetServicePlan() == globalcommonv1.ServicePlan_Essential_V2 &&
		!sharedPoolHighAvailabilityMatches(pool.GetHighAvailabilityType(), rp) {
		return nil, nil //nolint:nilnil // pool HA mismatch, fall through
	}
	return rp, nil
}

func compatibleSharedPoolHighAvailabilityTypes(
	highAvailabilityType globalngv1.ResourcePool_HighAvailabilityType,
) []globalngv1.ResourcePool_HighAvailabilityType {
	switch highAvailabilityType {
	case globalngv1.ResourcePool_REGIONAL:
		return []globalngv1.ResourcePool_HighAvailabilityType{globalngv1.ResourcePool_REGIONAL}
	case globalngv1.ResourcePool_ZONAL:
		return []globalngv1.ResourcePool_HighAvailabilityType{globalngv1.ResourcePool_ZONAL}
	default:
		return nil
	}
}

func sharedPoolHighAvailabilityMatches(
	requested globalngv1.ResourcePool_HighAvailabilityType,
	resourcePool *entity.ResourcePool,
) bool {
	if requested == globalngv1.ResourcePool_HIGH_AVAILABILITY_TYPE_UNSPECIFIED {
		return true
	}
	if resourcePool == nil {
		return false
	}

	actual := resourcePool.GetHighAvailabilityType()
	if actual == globalngv1.ResourcePool_HIGH_AVAILABILITY_TYPE_UNSPECIFIED {
		return false
	}
	return requested == actual
}
