package region

import (
	"strconv"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	openapingv1beta2 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/nextgen/v1beta2"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func resourcePoolCIDRHighAvailabilityTypeToGlobal(
	in openapingv1beta2.ByocVcpuPool_HighAvailabilityType,
) (globalngv1.ResourcePool_HighAvailabilityType, error) {
	switch in {
	case openapingv1beta2.ByocVcpuPool_REGIONAL:
		return globalngv1.ResourcePool_REGIONAL, nil
	case openapingv1beta2.ByocVcpuPool_ZONAL:
		return globalngv1.ResourcePool_ZONAL, nil
	case openapingv1beta2.ByocVcpuPool_HIGH_AVAILABILITY_TYPE_UNSPECIFIED:
		return globalngv1.ResourcePool_HIGH_AVAILABILITY_TYPE_UNSPECIFIED, nil
	default:
		return 0, bizerr.New(bizerr.ErrCodeParams, "invalid resource pool high availability type")
	}
}

func planResourcePoolCIDRResponseToOpenAPI(
	response *globalngv1.PlanResourcePoolCidrResponse,
) (*openapingv1beta2.PlanResourcePoolCidrResponse, error) {
	if response == nil || response.GetPolicy() == nil {
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "resource pool CIDR plan response is incomplete")
	}
	regionID, provider, err := openAPIRegionIdentity(response.GetCloudProviderId(), response.GetRegionId())
	if err != nil {
		return nil, err
	}
	status, err := recommendationStatusToOpenAPI(response.GetRecommendationStatus())
	if err != nil {
		return nil, err
	}
	exclusions, err := cidrExclusionsToOpenAPI(response.GetExclusions())
	if err != nil {
		return nil, err
	}
	result := &openapingv1beta2.PlanResourcePoolCidrResponse{
		RegionId:             regionID,
		CloudProvider:        provider,
		Policy:               cidrPolicyToOpenAPI(response.GetPolicy()),
		Exclusions:           exclusions,
		RecommendationStatus: status,
	}
	if response.Recommendation != nil {
		if status != openapingv1beta2.PlanResourcePoolCidrResponse_AVAILABLE || response.GetRecommendation().GetSupportedNodeCount() <= 0 {
			return nil, bizerr.New(bizerr.ErrCodeUnknown, "resource pool CIDR recommendation response is inconsistent")
		}
		result.Recommendation = &openapingv1beta2.ResourcePoolCidrRecommendation{
			Cidr:               response.GetRecommendation().GetCidr(),
			SupportedNodeCount: response.GetRecommendation().GetSupportedNodeCount(),
		}
	} else if status != openapingv1beta2.PlanResourcePoolCidrResponse_EXHAUSTED {
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "resource pool CIDR recommendation is missing")
	}
	return result, nil
}

func validateResourcePoolCIDRResponseToOpenAPI(
	response *globalngv1.ValidateResourcePoolCidrResponse,
) (*openapingv1beta2.ValidateResourcePoolCidrResponse, error) {
	if response == nil {
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "resource pool CIDR validation response is incomplete")
	}
	regionID, provider, err := openAPIRegionIdentity(response.GetCloudProviderId(), response.GetRegionId())
	if err != nil {
		return nil, err
	}
	findings, err := cidrFindingsToOpenAPI(response.GetBlockingFindings())
	if err != nil {
		return nil, err
	}
	capacityMustBeAbsent := false
	for _, finding := range findings {
		switch finding.GetCode() {
		case openapingv1beta2.ResourcePoolCidrValidationFinding_INVALID_CIDR,
			openapingv1beta2.ResourcePoolCidrValidationFinding_CIDR_NOT_PRIVATE,
			openapingv1beta2.ResourcePoolCidrValidationFinding_CIDR_PREFIX_UNSUPPORTED:
			capacityMustBeAbsent = true
		default:
		}
	}
	if capacityMustBeAbsent && response.SupportedNodeCount != nil {
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "blocked CIDR syntax or prefix contains a supported node count")
	}
	if !capacityMustBeAbsent && response.SupportedNodeCount == nil {
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "eligible CIDR response omits supported node count")
	}
	if response.SupportedNodeCount != nil && response.GetSupportedNodeCount() <= 0 {
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "supported node count must be positive")
	}
	return &openapingv1beta2.ValidateResourcePoolCidrResponse{
		RegionId:           regionID,
		CloudProvider:      provider,
		Cidr:               response.GetCidr(),
		SupportedNodeCount: response.SupportedNodeCount,
		BlockingFindings:   findings,
	}, nil
}

func openAPIRegionIdentity(
	providerID globalcommonv1.CloudProvider_Name,
	regionID globalcommonv1.Region_Name,
) (string, openapicommonv1beta1.Region_CloudProvider, error) {
	var expectedProvider openapicommonv1beta1.Region_CloudProvider
	switch providerID {
	case globalcommonv1.CloudProvider_AWS:
		expectedProvider = openapicommonv1beta1.Region_aws
	case globalcommonv1.CloudProvider_GCP:
		expectedProvider = openapicommonv1beta1.Region_gcp
	case globalcommonv1.CloudProvider_AZURE:
		expectedProvider = openapicommonv1beta1.Region_azure
	case globalcommonv1.CloudProvider_ALICLOUD:
		expectedProvider = openapicommonv1beta1.Region_alicloud
	default:
		return "", openapicommonv1beta1.Region_CLOUD_PROVIDER_UNSPECIFIED,
			bizerr.New(bizerr.ErrCodeUnknown, "invalid region identity returned by Global")
	}
	_, publicRegionID, provider, err := common.BuildOpenApiRegionName(providerID.String(), regionID.String())
	if err != nil || publicRegionID == "" || provider != expectedProvider {
		return "", openapicommonv1beta1.Region_CLOUD_PROVIDER_UNSPECIFIED,
			bizerr.New(bizerr.ErrCodeUnknown, "invalid region identity returned by Global")
	}
	return publicRegionID, provider, nil
}

func recommendationStatusToOpenAPI(
	status globalngv1.PlanResourcePoolCidrResponse_RecommendationStatus,
) (openapingv1beta2.PlanResourcePoolCidrResponse_RecommendationStatus, error) {
	switch status {
	case globalngv1.PlanResourcePoolCidrResponse_AVAILABLE:
		return openapingv1beta2.PlanResourcePoolCidrResponse_AVAILABLE, nil
	case globalngv1.PlanResourcePoolCidrResponse_EXHAUSTED:
		return openapingv1beta2.PlanResourcePoolCidrResponse_EXHAUSTED, nil
	default:
		return openapingv1beta2.PlanResourcePoolCidrResponse_RECOMMENDATION_STATUS_UNSPECIFIED,
			bizerr.New(bizerr.ErrCodeUnknown, "unknown resource pool CIDR recommendation status")
	}
}

func cidrPolicyToOpenAPI(policy *globalngv1.ResourcePoolCidrPolicy) *openapingv1beta2.ResourcePoolCidrPolicy {
	ranges := make([]*openapingv1beta2.ResourcePoolCidrPrefixRange, 0, len(policy.GetAllowedPrefixRanges()))
	for _, prefixRange := range policy.GetAllowedPrefixRanges() {
		ranges = append(ranges, &openapingv1beta2.ResourcePoolCidrPrefixRange{
			MinPrefixLength: prefixRange.GetMinPrefixLength(),
			MaxPrefixLength: prefixRange.GetMaxPrefixLength(),
		})
	}
	return &openapingv1beta2.ResourcePoolCidrPolicy{
		SupportedPrivateCidrs: append([]string(nil), policy.GetSupportedPrivateCidrs()...),
		AllowedPrefixRanges:   ranges,
	}
}

func cidrExclusionsToOpenAPI(
	exclusions []*globalngv1.ResourcePoolCidrExclusion,
) ([]*openapingv1beta2.ResourcePoolCidrExclusion, error) {
	result := make([]*openapingv1beta2.ResourcePoolCidrExclusion, 0, len(exclusions))
	for _, exclusion := range exclusions {
		converted, err := cidrExclusionToOpenAPI(exclusion)
		if err != nil {
			return nil, err
		}
		result = append(result, converted)
	}
	return result, nil
}

func cidrExclusionToOpenAPI(
	exclusion *globalngv1.ResourcePoolCidrExclusion,
) (*openapingv1beta2.ResourcePoolCidrExclusion, error) {
	if exclusion == nil {
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "CIDR exclusion is missing")
	}
	converted := &openapingv1beta2.ResourcePoolCidrExclusion{Cidr: exclusion.GetCidr()}
	switch exclusion.GetType() {
	case globalngv1.ResourcePoolCidrExclusion_RESOURCE_POOL:
		if exclusion.ResourcePoolId == nil || exclusion.ResourcePoolDisplayName == nil ||
			exclusion.RegionId == nil || exclusion.CloudProviderId == nil {
			return nil, bizerr.New(bizerr.ErrCodeUnknown, "resource pool CIDR exclusion is incomplete")
		}
		converted.Type = openapingv1beta2.ResourcePoolCidrExclusion_RESOURCE_POOL
		resourcePoolID := strconv.FormatUint(exclusion.GetResourcePoolId(), 10)
		converted.ResourcePoolId = &resourcePoolID
		displayName := exclusion.GetResourcePoolDisplayName()
		converted.ResourcePoolDisplayName = &displayName
		regionID, provider, err := openAPIRegionIdentity(exclusion.GetCloudProviderId(), exclusion.GetRegionId())
		if err != nil {
			return nil, err
		}
		converted.RegionId = &regionID
		converted.CloudProvider = &provider
	case globalngv1.ResourcePoolCidrExclusion_O11Y_CLUSTER:
		if hasResourcePoolExclusionDetails(exclusion) {
			return nil, bizerr.New(bizerr.ErrCodeUnknown, "O11Y CIDR exclusion contains resource pool details")
		}
		converted.Type = openapingv1beta2.ResourcePoolCidrExclusion_O11Y_CLUSTER
	case globalngv1.ResourcePoolCidrExclusion_TIDB_CLOUD_RESERVED:
		if hasResourcePoolExclusionDetails(exclusion) {
			return nil, bizerr.New(bizerr.ErrCodeUnknown, "reserved CIDR exclusion contains resource pool details")
		}
		converted.Type = openapingv1beta2.ResourcePoolCidrExclusion_TIDB_CLOUD_RESERVED
	default:
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "unknown CIDR exclusion type")
	}
	return converted, nil
}

func cidrFindingsToOpenAPI(
	findings []*globalngv1.ResourcePoolCidrValidationFinding,
) ([]*openapingv1beta2.ResourcePoolCidrValidationFinding, error) {
	result := make([]*openapingv1beta2.ResourcePoolCidrValidationFinding, 0, len(findings))
	for _, finding := range findings {
		if finding == nil {
			return nil, bizerr.New(bizerr.ErrCodeUnknown, "CIDR validation finding is missing")
		}
		code, err := cidrFindingCodeToOpenAPI(finding.GetCode())
		if err != nil {
			return nil, err
		}
		converted := &openapingv1beta2.ResourcePoolCidrValidationFinding{Code: code, Message: finding.GetMessage()}
		requiresConflict := isOverlapFinding(finding.GetCode())
		if requiresConflict != (finding.ConflictingExclusion != nil) {
			return nil, bizerr.New(bizerr.ErrCodeUnknown, "CIDR validation finding conflict details are inconsistent")
		}
		if requiresConflict {
			converted.ConflictingExclusion, err = cidrExclusionToOpenAPI(finding.GetConflictingExclusion())
			if err != nil {
				return nil, err
			}
		}
		result = append(result, converted)
	}
	return result, nil
}

func hasResourcePoolExclusionDetails(exclusion *globalngv1.ResourcePoolCidrExclusion) bool {
	return exclusion.ResourcePoolId != nil || exclusion.ResourcePoolDisplayName != nil ||
		exclusion.RegionId != nil || exclusion.CloudProviderId != nil
}

func isOverlapFinding(code globalngv1.ResourcePoolCidrValidationFinding_Code) bool {
	switch code {
	case globalngv1.ResourcePoolCidrValidationFinding_CIDR_OVERLAPS_RESOURCE_POOL,
		globalngv1.ResourcePoolCidrValidationFinding_CIDR_OVERLAPS_O11Y,
		globalngv1.ResourcePoolCidrValidationFinding_CIDR_OVERLAPS_RESERVED_NETWORK:
		return true
	default:
		return false
	}
}

func cidrFindingCodeToOpenAPI(
	code globalngv1.ResourcePoolCidrValidationFinding_Code,
) (openapingv1beta2.ResourcePoolCidrValidationFinding_Code, error) {
	switch code {
	case globalngv1.ResourcePoolCidrValidationFinding_INVALID_CIDR:
		return openapingv1beta2.ResourcePoolCidrValidationFinding_INVALID_CIDR, nil
	case globalngv1.ResourcePoolCidrValidationFinding_CIDR_NOT_PRIVATE:
		return openapingv1beta2.ResourcePoolCidrValidationFinding_CIDR_NOT_PRIVATE, nil
	case globalngv1.ResourcePoolCidrValidationFinding_CIDR_PREFIX_UNSUPPORTED:
		return openapingv1beta2.ResourcePoolCidrValidationFinding_CIDR_PREFIX_UNSUPPORTED, nil
	case globalngv1.ResourcePoolCidrValidationFinding_CIDR_OVERLAPS_RESOURCE_POOL:
		return openapingv1beta2.ResourcePoolCidrValidationFinding_CIDR_OVERLAPS_RESOURCE_POOL, nil
	case globalngv1.ResourcePoolCidrValidationFinding_CIDR_OVERLAPS_O11Y:
		return openapingv1beta2.ResourcePoolCidrValidationFinding_CIDR_OVERLAPS_O11Y, nil
	case globalngv1.ResourcePoolCidrValidationFinding_CIDR_OVERLAPS_RESERVED_NETWORK:
		return openapingv1beta2.ResourcePoolCidrValidationFinding_CIDR_OVERLAPS_RESERVED_NETWORK, nil
	default:
		return openapingv1beta2.ResourcePoolCidrValidationFinding_CODE_UNSPECIFIED,
			bizerr.New(bizerr.ErrCodeUnknown, "unknown CIDR validation finding code")
	}
}
