package vpc

import (
	"context"
	"strconv"

	"github.com/samber/lo"
	regionaldedicatedv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/endpoint"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/service/workflow/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/enum"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

func (s *serviceImpl) AddSecondaryCidr(
	ctx context.Context,
	params *globaldedicatedv1.AddSecondaryCidrRequest,
) (*globaldedicatedv1.Vpc, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "Domain.AddSecondaryCidrSvc")
	defer span.End()

	if err := s.validateSecondaryCidr(params); err != nil {
		return nil, err
	}

	regionName, err := enum.FromString[globalcommonv1.Region_Name](params.GetRegionName())
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid region")
	}
	cloudProviderName, rawErr := enum.FromString[globalcommonv1.CloudProvider_Name](params.GetCloudProviderName())
	if rawErr != nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "invalid cloud provider")
	}
	if cloudProviderName != int32(globalcommonv1.CloudProvider_AWS) {
		return nil, bizerr.New(bizerr.ErrCodeParams, "unsupported cloud provider")
	}

	var globalVpc *globaldedicatedv1.Vpc
	transErr := s.transaction.Transaction(ctx, func(ctx context.Context) error {
		// check if vpc peering is enabled, if enabled, return error
		peering, err := s.vpcPeeringRepo.Query(ctx, &endpoint.VpcPeeringFilter{
			CloudProviderNames: []entity.CloudProviderName{entity.CloudProviderName(cloudProviderName)},
			RegionNames:        []entity.RegionName{entity.RegionName(regionName)},
			ProjectIds:         []uint64{params.GetProjectId()},
			OrganizationIds:    []uint64{params.GetOrganizationId()},
		}, nil, nil)
		if err != nil {
			return bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to get vpc peering")
		}
		if len(peering) > 0 {
			return bizerr.New(
				bizerr.ErrCodeParams,
				"secondary cidr is not supported for vpc that has enabled vpc peering",
			)
		}
		regionEntity, err := s.regionRepo.SelectRegionById(
			ctx,
			globalcommonv1.Region_Name(regionName),
			params.GetOrganizationId(),
		)
		if err != nil {
			return bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid region")
		}
		if regionEntity.CloudProviderId != globalcommonv1.CloudProvider_Name(cloudProviderName) {
			return bizerr.New(
				bizerr.ErrCodeParams,
				"unsupported region %s in %s cloud provider",
				params.GetRegionName(),
				params.GetCloudProviderName(),
			)
		}

		listVpcsRequest := &regionaldedicatedv1.ListVpcsRequest{
			TenantId:  strconv.FormatUint(params.GetOrganizationId(), 10),
			ProjectId: strconv.FormatUint(params.GetProjectId(), 10),
			ClusterId: "", // leave it empty
			Statuses: []regionaldedicatedv1.Vpc_Status{
				regionaldedicatedv1.Vpc_ACTIVE,
			},
		}
		listRes, err := s.regionServerCli.ListVpcs(ctx, regionEntity, listVpcsRequest)
		if err != nil {
			return err
		}
		if len(listRes.Vpcs) == 0 {
			return bizerr.New(bizerr.ErrCodeVPCPeeringCreatePrerequisite, ErrMsgVPCPeeringCreatePrerequisite)
		}
		targetVpc, ok := lo.Find(listRes.Vpcs, func(vpc *regionaldedicatedv1.Vpc) bool {
			return vpc.GetVpcId() == params.GetVpcId()
		})
		if !ok {
			return bizerr.New(bizerr.ErrCodeParams, "vpc_id is invalid")
		}
		if targetVpc.GetStatus() != regionaldedicatedv1.Vpc_ACTIVE {
			return bizerr.New(bizerr.ErrCodeParams, "vpc is not active")
		}

		// check if the secondary cidr is valid
		addRequest := &regionaldedicatedv1.AddSecondaryCidrRequest{
			VpcId:         params.GetVpcId(),
			SecondaryCidr: params.GetSecondaryCidr(),
			ValidateOnly:  true,
		}
		addRes, err := s.regionServerCli.AddSecondaryCidr(ctx, regionEntity, addRequest)
		if err != nil {
			return err
		}
		globalVpc, err = vpcRegionToGlobal(addRes, regionEntity)
		if err != nil {
			return bizerr.WrapError(err, "failed to convert regional vpc to global vpc")
		}
		// if validate only, return
		if params.GetValidateOnly() {
			return nil
		}

		// create secondary cidr db record
		secondaryCidrID := s.idGeneratorFacade.MustNew()
		if err := s.cidrRepo.CreateSecondaryCidr(ctx, &entity.CIDR{
			ID:          secondaryCidrID,
			ProjectID:   params.GetProjectId(),
			TenantID:    params.GetOrganizationId(),
			BasicRegion: entity.RegionToBasicRegion(regionEntity),
			CIDR:        params.GetSecondaryCidr(),
			VpcID:       params.GetVpcId(),
		}); err != nil {
			return bizerr.WrapError(err, "failed to create secondary cidr")
		}

		// create async workflow to add secondary cidr
		workFlowErr := s.workflow.RunDag(
			ctx,
			common.AddSecondaryCidr,
			strconv.FormatUint(params.GetOrganizationId(), 10),
			strconv.FormatUint(params.GetProjectId(), 10),
			map[string]string{
				workflow.KeyTraceId:       workflow.GetTraceId(ctx),
				common.KeySecondaryCidrId: strconv.FormatUint(secondaryCidrID, 10),
			},
		)
		if workFlowErr != nil {
			return bizerr.WrapErrorWithCode(workFlowErr, bizerr.ErrCodeUnknown, "create async workflow failed")
		}
		return nil
	})
	if transErr != nil {
		return nil, bizerr.WrapError(transErr, "failed to add secondary cidr")
	}

	return globalVpc, nil
}

func (s *serviceImpl) validateSecondaryCidr(
	params *globaldedicatedv1.AddSecondaryCidrRequest,
) bizerr.BizError {
	if params.GetSecondaryCidr() == "" {
		return bizerr.New(bizerr.ErrCodeParams, "secondary_cidr is required")
	}

	if params.GetVpcId() == "" {
		return bizerr.New(bizerr.ErrCodeParams, "vpc_id is required")
	}

	if params.GetProjectId() == 0 {
		return bizerr.New(bizerr.ErrCodeParams, "project_id is required")
	}

	if params.GetCloudProviderName() == "" {
		return bizerr.New(bizerr.ErrCodeParams, "cloud_provider_name is required")
	}

	if params.GetRegionName() == "" {
		return bizerr.New(bizerr.ErrCodeParams, "region_name is required")
	}

	return nil
}
