package region

import (
	"cmp"
	"context"
	"slices"

	"github.com/samber/lo"
	"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"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func toDefaultRegionProviderIdentity(di *entity.DefaultProviderIdentity) *globalngv1.DefaultRegionProviderIdentity {
	if di == nil {
		return nil
	}
	servicePlan := globalcommonv1.ServicePlan(globalcommonv1.ServicePlan_value[di.ServicePlan])
	return &globalngv1.DefaultRegionProviderIdentity{
		Id:          uint64(di.ID),
		TenantId:    di.TenantId,
		Identity:    di.Identity,
		ServicePlan: servicePlan,
	}
}

func (s *service) RegisterRegionProviderIdentity(ctx context.Context,
	request *globalngv1.RegisterRegionProviderIdentityRequest,
) (*globalngv1.RegionProviderIdentity, bizerr.BizError) {
	if request.GetRegionLogicalName() == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "region logical name is required")
	}

	region, err := s.regionRepo.GetByLogicalName(ctx, request.GetRegionLogicalName())
	if err != nil {
		return nil, err
	}

	identity := request.GetRegionProviderIdentity()

	newIdentityId := s.idGeneratorAdapter.MustNew()
	identity.Id = newIdentityId

	err = s.regionProviderIdentityRepo.Save(ctx, &entity.RegionProviderIdentity{
		RegionProviderIdentity: identity,
		RegionId:               region.RegionID,
	})
	if err != nil {
		return nil, bizerr.New(bizerr.GeneralInternalErrorCode, "failed to save provider identity")
	}

	// If is_default is true, create/update DefaultProviderIdentity with TenantId=0
	if identity.GetIsDefault() {
		defaultIdentity := &entity.DefaultProviderIdentity{
			ProviderId:  region.ProviderID,
			ServicePlan: region.ServicePlan.String(),
			RegionId:    region.RegionID,
			TenantId:    0, // Default for all tenants
			Identity:    identity.GetIdentity(),
		}
		err = s.regionProviderIdentityRepo.SaveDefaultProviderIdentity(ctx, defaultIdentity)
		if err != nil {
			return nil, bizerr.WrapError(err, "failed to save default provider identity")
		}
	}

	return identity, nil
}

func (s *service) ListRegionProviderIdentities(ctx context.Context,
	request *globalngv1.ListRegionProviderIdentitiesRequest,
) (*globalngv1.ListRegionProviderIdentitiesResponse, bizerr.BizError) {
	if request.RegionLogicalName == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "region logical name is required")
	}

	region, err := s.regionRepo.GetByLogicalName(ctx, request.RegionLogicalName)
	if err != nil {
		return nil, err
	}

	identities, err := s.regionProviderIdentityRepo.ListProviderIdentityByRegion(ctx, region.RegionID)

	if err != nil {
		return nil, err
	}

	apiIdentities := lo.Map(identities, func(item *entity.RegionProviderIdentity, index int) *globalngv1.RegionProviderIdentity {
		return item.RegionProviderIdentity
	})

	defaultIdentities, err := s.regionProviderIdentityRepo.ListDefaultProviderIdentitiesByRegion(
		ctx,
		region.ProviderID,
		region.RegionID,
	)
	if err != nil {
		return nil, err
	}

	return &globalngv1.ListRegionProviderIdentitiesResponse{
		Region:                   region.Region,
		RegionProviderIdentities: apiIdentities,
		DefaultProviderIdentities: lo.Map(
			defaultIdentities,
			func(item *entity.DefaultProviderIdentity, _ int) *globalngv1.DefaultRegionProviderIdentity {
				return toDefaultRegionProviderIdentity(item)
			},
		),
	}, nil
}

// PickRegionProviderIdentity picks the most appropriate RegionProviderIdentity
// for the given regionId and tenantId based on the following priority:
//
//	1: Match both regionId and tenantId
//	2: Match tenantId, regionId = 0
//	3: Match regionId, tenantId = 0
//	4: regionId = 0 and tenantId = 0
//
// If no default identity is found, it falls back to the first available identity
// in the RegionProviderIdentity table for the specified region.
func (s *service) PickRegionProviderIdentity(ctx context.Context,
	regionId uint64,
	tenantId uint64) (*globalngv1.RegionProviderIdentity, bizerr.BizError) {
	// Get the region to extract provider ID and service plan
	region, err := s.regionRepo.Get(ctx, regionId)
	if err != nil {
		return nil, bizerr.WrapError(err, "failed to get region")
	}

	providerId := region.ProviderID
	servicePlan := region.ServicePlan.String()

	// Query default provider identities
	defaultIdentities, err := s.regionProviderIdentityRepo.QueryDefaultProviderIdentity(ctx, providerId, servicePlan, regionId, tenantId)
	if err != nil {
		return nil, bizerr.WrapError(err, "failed to query default provider identities")
	}

	getPriority := func(di *entity.DefaultProviderIdentity) int {
		switch {
		case di.RegionId == regionId && di.TenantId == tenantId:
			return 1

		case di.RegionId == 0 && di.TenantId == tenantId:
			return 2
		case di.RegionId == regionId && di.TenantId == 0:
			return 3
		case di.RegionId == 0 && di.TenantId == 0:
			return 4
		default:
			return 5 // Should not happen based on our query
		}
	}

	slices.SortFunc(defaultIdentities, func(a, b *entity.DefaultProviderIdentity) int {
		return cmp.Compare(getPriority(a), getPriority(b))
	})

	// Try to find a valid identity from the candidates
	for _, candidate := range defaultIdentities {
		// Check if the identity exists in the ProviderIdentity table
		providerIdentity, err := s.regionProviderIdentityRepo.GetProviderIdentityByIdentity(ctx, regionId, candidate.Identity)
		if err != nil {
			// Log error and skip this candidate
			tilog.Error(ctx, "DefaultProviderIdentity has invalid identity that does not exist in ProviderIdentity table",
				zap.Uint64("default_provider_identity_id", uint64(candidate.ID)),
				zap.Uint64("provider_id", candidate.ProviderId),
				zap.String("service_plan", candidate.ServicePlan),
				zap.Uint64("region_id", candidate.RegionId),
				zap.Uint64("tenant_id", candidate.TenantId),
				zap.String("identity", candidate.Identity),
				zap.Error(err))
			continue
		}
		return providerIdentity.RegionProviderIdentity, nil
	}

	// If no valid default identity found, use the first result from ListRegionProviderIdentities
	identities, err := s.regionProviderIdentityRepo.ListProviderIdentityByRegion(ctx, regionId)
	if err != nil {
		return nil, bizerr.WrapError(err, "failed to list region provider identities")
	}

	if len(identities) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "no provider identities found for region %d", regionId)
	}

	return identities[0].RegionProviderIdentity, nil
}
