package resourcepool

import (
	"cmp"
	"context"
	"slices"
	"strings"

	"google.golang.org/grpc/codes"
	"google.golang.org/protobuf/proto"

	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 usesOrganizationNameNamespace(resourcePool *globalngv1.ResourcePool) bool {
	// Names are organization-scoped for every organization-owned service plan;
	// platform-owned shared pools do not participate in name uniqueness.
	return resourcePool.GetOwnerType() == globalngv1.ResourcePool_ORGANIZATION
}

func usesCustomerCIDRNamespace(resourcePool *globalngv1.ResourcePool) bool {
	// CIDR policy and overlap checks remain customer-managed BYOC behavior only.
	return usesOrganizationNameNamespace(resourcePool) &&
		resourcePool.GetServicePlan() == globalcommonv1.ServicePlan_BYOC
}

func (s *serviceImpl) lockAndValidateOrganizationNamespace(
	ctx context.Context,
	resourcePool *globalngv1.ResourcePool,
	excludeResourcePoolID uint64,
	checkCIDR bool,
) bizerr.BizError {
	if !usesOrganizationNameNamespace(resourcePool) {
		return nil
	}
	if resourcePool.GetOwnerId() == 0 {
		return bizerr.New(bizerr.ErrCodeParams, "organization id is required")
	}

	if err := s.resourcePoolRepo.LockOrganizationNamespace(ctx, resourcePool.GetOwnerId()); err != nil {
		return bizerr.WrapError(err, "lock organization resource pool namespace")
	}
	return s.validateOrganizationNamespaceLocked(ctx, resourcePool, excludeResourcePoolID, checkCIDR)
}

func (s *serviceImpl) validateOrganizationNamespaceLocked(
	ctx context.Context,
	resourcePool *globalngv1.ResourcePool,
	excludeResourcePoolID uint64,
	checkCIDR bool,
) bizerr.BizError {
	if !usesOrganizationNameNamespace(resourcePool) {
		return nil
	}

	if err := s.validateOrganizationNameLocked(ctx, resourcePool, excludeResourcePoolID); err != nil {
		return err
	}

	if !checkCIDR || !usesCustomerCIDRNamespace(resourcePool) {
		return nil
	}
	cidrStart, cidrEnd, parseErr := entity.CanonicalIPv4Range(resourcePool.GetCidr())
	if parseErr != nil {
		return bizerr.WrapErrorWithCode(parseErr, bizerr.ErrCodeParams, "invalid resource pool CIDR")
	}
	cidrOverlaps, err := s.resourcePoolRepo.HasLiveCIDROverlap(
		ctx,
		resourcePool.GetOwnerId(),
		resourcePool.GetProviderId(),
		resourcePool.GetRegionId(),
		cidrStart,
		cidrEnd,
		excludeResourcePoolID,
	)
	if err != nil {
		return bizerr.WrapError(err, "check resource pool CIDR overlap")
	}
	if cidrOverlaps {
		return bizerr.New(bizerr.ErrCodeParams, "resource pool CIDR overlaps an existing resource pool")
	}

	return nil
}

func (s *serviceImpl) validateOrganizationNameLocked(
	ctx context.Context,
	resourcePool *globalngv1.ResourcePool,
	excludeResourcePoolID uint64,
) bizerr.BizError {
	resourcePool.Name = strings.TrimSpace(resourcePool.GetName())
	normalizedName := entity.NormalizeResourcePoolName(resourcePool.GetName())
	if normalizedName == "" {
		return bizerr.New(bizerr.ErrCodeParams, "resource pool name is required")
	}
	nameExists, err := s.resourcePoolRepo.HasLiveNormalizedName(
		ctx,
		resourcePool.GetOwnerId(),
		normalizedName,
		excludeResourcePoolID,
	)
	if err != nil {
		return bizerr.WrapError(err, "check resource pool name")
	}
	if nameExists {
		return bizerr.New(bizerr.ErrCodeParams, "resource pool name already exists")
	}

	return nil
}

func (s *serviceImpl) lockAndValidateOrganizationNamespaceForCreate(
	ctx context.Context,
	resourcePool *globalngv1.ResourcePool,
) bizerr.BizError {
	if !usesOrganizationNameNamespace(resourcePool) {
		return nil
	}
	if err := s.resourcePoolRepo.LockOrganizationNamespace(ctx, resourcePool.GetOwnerId()); err != nil {
		return bizerr.WrapError(err, "lock organization resource pool namespace")
	}
	if err := s.validateOrganizationNameLocked(ctx, resourcePool, 0); err != nil {
		return err
	}
	if !usesCustomerCIDRNamespace(resourcePool) {
		return nil
	}
	candidate, parseErr := parseCanonicalIPv4CIDR(resourcePool.GetCidr())
	if parseErr != nil {
		return bizerr.New(bizerr.ErrCode(codes.Internal), "validated resource pool CIDR became invalid")
	}
	pools, err := s.resourcePoolRepo.ListLiveBYOCOrganizationResourcePoolsInRegion(
		ctx,
		resourcePool.GetOwnerId(),
		resourcePool.GetProviderId(),
		resourcePool.GetRegionId(),
	)
	if err != nil {
		return err
	}
	findings := make([]*globalngv1.ResourcePoolCidrValidationFinding, 0)
	for _, pool := range pools {
		parsed, parseErr := parseCanonicalIPv4CIDR(pool.GetCidr())
		if parseErr != nil {
			return bizerr.New(bizerr.ErrCode(codes.Internal), "live resource pool %d has invalid CIDR", pool.GetId())
		}
		if !intervalsOverlap(candidate.interval, parsed.interval) {
			continue
		}
		exclusion := &globalngv1.ResourcePoolCidrExclusion{
			Type:                    globalngv1.ResourcePoolCidrExclusion_RESOURCE_POOL,
			Cidr:                    parsed.canonical,
			ResourcePoolId:          proto.Uint64(pool.GetId()),
			ResourcePoolDisplayName: proto.String(pool.GetName()),
			RegionId:                enumPointer(pool.GetRegionId()),
			CloudProviderId:         enumPointer(pool.GetProviderId()),
		}
		findings = append(findings, cidrFinding(
			globalngv1.ResourcePoolCidrValidationFinding_CIDR_OVERLAPS_RESOURCE_POOL,
			"The CIDR overlaps an existing resource pool.",
			exclusion,
		))
	}
	slices.SortFunc(findings, func(leftFinding, rightFinding *globalngv1.ResourcePoolCidrValidationFinding) int {
		left := leftFinding.GetConflictingExclusion()
		right := rightFinding.GetConflictingExclusion()
		leftCIDR, _ := parseCanonicalIPv4CIDR(left.GetCidr())
		rightCIDR, _ := parseCanonicalIPv4CIDR(right.GetCidr())
		if result := cmp.Compare(leftCIDR.interval.start, rightCIDR.interval.start); result != 0 {
			return result
		}
		return cmp.Compare(left.GetResourcePoolId(), right.GetResourcePoolId())
	})
	if len(findings) > 0 {
		return newCIDRValidationFailure(findings)
	}
	return nil
}
