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"
	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/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 (
	AcceptVpcPeering = "accept_vpc_peering"
)

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

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

func (c *AcceptVpcPeeringAction) Name() string {
	return AcceptVpcPeering
}

func (c *AcceptVpcPeeringAction) 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 accept vpc peering", zap.String("vpc_peering_id", peeringId))
	defer func() {
		tilog.Info(cctx, "end to accept 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
	}

	// only auto accept dbass vpc peering
	if vpcPeering.Type == globaldedicatedv1.VpcPeering_CUSTOMER {
		tilog.Info(cctx, "vpc peering is customer type, skip", zap.Uint64("vpc_peering_id", vpcPeering.GetId()))
		return nil
	}

	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
	}

	peerRegionName, err := enum.FromString[globalcommonv1.Region_Name](vpcPeering.GetCustomerRegion())
	if err != nil {
		tilog.Error(
			cctx,
			"failed to parse peer region name",
			zap.Error(err),
			zap.Uint64("vpc_peering_id", vpcPeering.GetId()),
		)
		return err
	}
	peerRegionEntity, err := c.regionRepo.SelectRegionById(
		cctx,
		globalcommonv1.Region_Name(peerRegionName),
		vpcPeering.OrganizationId,
	)
	if err != nil {
		tilog.Error(
			cctx,
			"find peer region by id failed",
			zap.Error(err),
			zap.Uint64("vpc_peering_id", vpcPeering.GetId()),
		)
		return err
	}

	getVpcPeeringReq := &regionaldedicatedv1.GetVpcPeeringRequest{
		VpcPeeringId:   strconv.FormatUint(vpcPeering.GetId(), 10),
		ShowStatusOnly: false,
	}

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

	if regionalVpcPerring.GetStatus() != regionaldedicatedv1.VpcPeering_PENDING {
		tilog.Info(
			cctx,
			"vpc peering status is not pending, skip",
			zap.Uint64("vpc_peering_id", vpcPeering.GetId()),
			zap.String("status", regionalVpcPerring.GetStatus().String()),
		)
		return nil
	}

	// accept vpc peering
	// req to peer region
	if err := c.accept(cctx, vpcPeering, regionalVpcPerring, peerRegionEntity); err != nil {
		tilog.Error(
			cctx,
			"failed to Accept vpc peering",
			zap.Uint64("vpc_peering_id", vpcPeering.GetId()),
			zap.Error(err),
		)
		return err
	}

	return nil
}

func (c *AcceptVpcPeeringAction) accept(
	ctx context.Context,
	vpcPeering *entity.VpcPeering,
	regioanlVpcPeering *regionaldedicatedv1.VpcPeering,
	regionEntity *entity.Region,
) error {
	acceptVpcPeeringRequest := &regionaldedicatedv1.AcceptVpcPeeringRequest{
		RequesterVpcCidr: regioanlVpcPeering.GetSourceCidrBlock(),
	}

	switch vpcPeering.CloudProvider {
	case globalcommonv1.CloudProvider_AWS:
		acceptVpcPeeringRequest.VpcPeeringId = regioanlVpcPeering.GetAws().GetVpcPeeringConnectionId()
		acceptVpcPeeringRequest.AccepterVpcId = vpcPeering.GetAws().GetCustomerVpcId()
	case globalcommonv1.CloudProvider_GCP:
		// acceptVpcPeeringRequest.VpcPeeringId = regioanlVpcPeering.GetGcp().GetSourceNetworkName()
		// acceptVpcPeeringRequest.AccepterVpcId = vpcPeering.GetGcp().GetCustomerProjectId()
		return bizerr.New(bizerr.ErrCodeUnknown, "not support gcp vpc peering")
	case globalcommonv1.CloudProvider_AZURE:
		// TODO @SabaPing
		return bizerr.New(bizerr.ErrCodeUnknown, "not support azure vpc peering")
	default:
		return bizerr.New(bizerr.ErrCodeUnknown, "invalid vpc peering cloud provider")
	}

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

	return nil
}
