package action

import (
	"context"
	"strconv"

	"github.com/tidbcloud/fastflow/pkg/entity/run"
	regionaldedicatedv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/regionserver"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	endpointrepo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/endpoint"
	regionrepo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/region"
	"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"
)

const (
	CreateVpcPeering = "create_vpc_peering"
)

type CreateVpcPeeringAction struct {
	regionRepo      regionrepo.Repo
	vpcPeeringRepo  endpointrepo.VpcPeeringRepo
	regionServerCli regionserver.Client
}

func NewCreateVpcPeeringAction(
	regionRepo regionrepo.Repo,
	vpcPeeringRepo endpointrepo.VpcPeeringRepo,
	regionServerCli regionserver.Client,
) *CreateVpcPeeringAction {
	return &CreateVpcPeeringAction{
		regionRepo:      regionRepo,
		vpcPeeringRepo:  vpcPeeringRepo,
		regionServerCli: regionServerCli,
	}
}

func (c *CreateVpcPeeringAction) Name() string {
	return CreateVpcPeering
}

func (c *CreateVpcPeeringAction) Run(ctx run.ExecuteContext, params any) error {
	cctx, span := workflow.GenerateContext(ctx)

	peeringId, ok := ctx.GetVar(common.KeyVpcPeeringId)
	if !ok {
		tilog.Error(cctx, "vpc_peering_id parameter not found")
		return bizerr.New(bizerr.ErrCodeParams, "vpc_peering_id parameter not found")
	}
	if peeringId == "" {
		tilog.Error(cctx, "peeringId parameter is empty")
		return bizerr.New(bizerr.ErrCodeParams, "peeringId parameter is empty")
	}
	vpcPeeringID, err := strconv.ParseUint(peeringId, 10, 64)
	if err != nil {
		tilog.Error(cctx, "failed to parse vpc peering id", zap.Error(err))
		return err
	}

	keyCloudProviderName, ok := ctx.GetVar(common.KeyCloudProviderName)
	if !ok {
		tilog.Error(cctx, "cloud_provider_name parameter not found")
		return bizerr.New(bizerr.ErrCodeParams, "cloud_provider_name parameter not found")
	}
	cloudProviderName, err := enum.FromString[globalcommonv1.CloudProvider_Name](keyCloudProviderName)
	if err != nil {
		return bizerr.New(bizerr.ErrCodeParams, "invalid cloud provider")
	}

	tilog.Info(cctx, "start to create vpc peering", zap.String("vpc_peering_id", peeringId))
	defer func() {
		tilog.Info(cctx, "end to create vpc peering", zap.String("vpc_peering_id", peeringId))
		span.End()
	}()

	vpcPeering, err := c.vpcPeeringRepo.Load(cctx, globalcommonv1.CloudProvider_Name(cloudProviderName), vpcPeeringID)
	if err != nil {
		tilog.Error(cctx, "failed to load vpc peering", zap.Uint64("vpc_peering_id", vpcPeeringID), zap.Error(err))
		return err
	}
	regionName, err := enum.FromString[globalcommonv1.Region_Name](vpcPeering.GetTidbCloudRegion())
	if err != nil {
		tilog.Error(
			cctx,
			"failed to parse region name",
			zap.Error(err),
			zap.Uint64("vpc_peering_id", vpcPeering.GetId()),
		)
		return err
	}
	regionEntity, err := c.regionRepo.SelectRegionById(
		cctx,
		globalcommonv1.Region_Name(regionName),
		vpcPeering.OrganizationId,
	)
	if err != nil {
		tilog.Error(cctx, "find region by id failed", zap.Error(err), zap.Uint64("vpc_peering_id", vpcPeering.GetId()))
		return err
	}

	listVpcsRequest := &regionaldedicatedv1.ListVpcsRequest{
		TenantId:  strconv.FormatUint(vpcPeering.GetOrganizationId(), 10),
		ProjectId: strconv.FormatUint(vpcPeering.GetProjectId(), 10),
		ClusterId: "", // leave it empty
		Statuses: []regionaldedicatedv1.Vpc_Status{
			regionaldedicatedv1.Vpc_ACTIVE,
			regionaldedicatedv1.Vpc_CREATING,
			regionaldedicatedv1.Vpc_FAILED,
		},
	}
	listRes, err := c.regionServerCli.ListVpcs(cctx, regionEntity, listVpcsRequest)
	if err != nil {
		return err
	}
	if len(listRes.Vpcs) != 1 {
		return bizerr.New(bizerr.ErrCodeUnknown, "number of vpcs is not 1")
	}
	vpc := listRes.Vpcs[0]

	if err := c.create(cctx, vpcPeering, vpc, regionEntity); err != nil {
		tilog.Error(
			cctx,
			"failed to create vpc peering",
			zap.Error(err),
			zap.Uint64("vpc_peering_id", vpcPeering.GetId()),
		)
		return err
	}

	return nil
}

func (c *CreateVpcPeeringAction) create(
	ctx context.Context,
	vpcPeering *entity.VpcPeering,
	vpc *regionaldedicatedv1.Vpc,
	regionEntity *entity.Region,
) error {
	createVpcPeeringRequest := &regionaldedicatedv1.CreateVpcPeeringRequest{
		VpcPeering: &regionaldedicatedv1.VpcPeering{
			Id:                   strconv.FormatUint(vpcPeering.GetId(), 10),
			TenantId:             strconv.FormatUint(vpcPeering.GetOrganizationId(), 10),
			ProjectId:            strconv.FormatUint(vpcPeering.GetProjectId(), 10),
			SourceCidrBlock:      vpc.CidrBlock,
			DestinationCidrBlock: vpcPeering.GetCustomerCidrBlock(),
			Info:                 nil, // fill later
			Status:               0,   // leave it empty
		},
	}
	switch vpc.Info.(type) {
	case *regionaldedicatedv1.Vpc_Aws:
		createVpcPeeringRequest.VpcPeering.Info = &regionaldedicatedv1.VpcPeering_Aws_{
			Aws: &regionaldedicatedv1.VpcPeering_Aws{
				SourceAccountId:        vpc.GetAws().AccountId,
				SourceVpcId:            vpc.GetAws().VpcId,
				DestinationAccountId:   vpcPeering.Aws.GetCustomerAccountId(),
				DestinationVpcId:       vpcPeering.Aws.GetCustomerVpcId(),
				VpcPeeringConnectionId: "", // leave it empty
				DestinationRegion:      vpcPeering.GetCustomerRegion(),
			},
		}
	case *regionaldedicatedv1.Vpc_Gcp:
		createVpcPeeringRequest.VpcPeering.Info = &regionaldedicatedv1.VpcPeering_Gcp_{
			Gcp: &regionaldedicatedv1.VpcPeering_Gcp{
				SourceProjectId:        vpc.GetGcp().ProjectId,
				SourceNetworkName:      vpc.GetGcp().NetworkName,
				DestinationProjectId:   vpcPeering.Gcp.GetCustomerProjectId(),
				DestinationNetworkName: vpcPeering.Gcp.GetCustomerNetworkName(),
			},
		}
	default:
		return bizerr.New(bizerr.ErrCodeUnknown, "invalid vpc info")
	}

	if _, err := c.regionServerCli.CreateVpcPeering(ctx, regionEntity, createVpcPeeringRequest); err != nil {
		tilog.Error(
			ctx,
			"failed to create vpc peering",
			zap.String("region_id", regionEntity.Id.String()),
			zap.Uint64("vpc_peering_id", vpcPeering.GetId()),
			zap.Error(err),
		)
		return err
	}

	return nil
}
