package byocvpcpeering

import (
	"context"
	"net"
	"strconv"
	"strings"

	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/nextgen-global-service/domain/repository/tidbgroup"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/resourcepoolpolicy"
	servicecommon "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/common"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/workflow/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	utilworkflow "github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

func (s *serviceImpl) UpdateVpcPeeringAllowlistSetting(
	ctx context.Context,
	req *globalngv1.UpdateVpcPeeringAllowlistSettingRequest,
) (*entity.VpcPeeringAllowlistSetting, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "ByocVpcPeeringService.UpdateVpcPeeringAllowlistSetting")
	defer span.End()

	if req.GetClusterId() == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster_id is required")
	}

	cluster, err := s.clusterRepo.Get(ctx, req.GetClusterId(), false)
	if err != nil {
		return nil, err
	}
	if cluster.State != globalngv1.Cluster_AVAILABLE {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster is not available")
	}
	ipAccessList, err := resolveIPAccessList(req)
	if err != nil {
		return nil, err
	}
	resourcePoolID, parseErr := strconv.ParseUint(cluster.GetSharedPoolId(), 10, 64)
	if parseErr != nil || resourcePoolID == 0 {
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "cluster has an invalid resource pool id")
	}
	resourcePool, err := s.resourcePoolRepo.Load(ctx, resourcePoolID, false)
	if err != nil {
		return nil, err
	}
	if lifecycleErr := servicecommon.RequireResourcePoolLifecycle(
		resourcePool,
		resourcepoolpolicy.OperationUpdateNetwork,
	); lifecycleErr != nil {
		return nil, lifecycleErr
	}

	for _, ip := range ipAccessList {
		if ip.GetCidrBlock() == "" {
			return nil, bizerr.New(bizerr.ErrCodeParams, "ip access list cidr block is required")
		}
		if _, _, err := net.ParseCIDR(ip.GetCidrBlock()); err != nil {
			return nil, bizerr.New(bizerr.ErrCodeParams, "ip access list cidr block %s is invalid", ip.GetCidrBlock())
		}
	}

	if ipAccessList == nil {
		ipAccessList = []*globalngv1.Endpoint_IpAddress{}
	}

	output := &entity.VpcPeeringAllowlistSetting{
		VpcPeeringAllowlistSetting: &globalngv1.VpcPeeringAllowlistSetting{
			ClusterId:    req.GetClusterId(),
			IpAccessList: ipAccessList,
		},
	}

	if err := s.transaction.Transaction(ctx, func(ctx context.Context) error {
		lockedResourcePool, lockErr := s.resourcePoolRepo.LoadForUpdate(ctx, resourcePoolID, false)
		if lockErr != nil {
			return lockErr
		}
		if lifecycleErr := servicecommon.RequireResourcePoolLifecycle(
			lockedResourcePool,
			resourcepoolpolicy.OperationUpdateNetwork,
		); lifecycleErr != nil {
			return lifecycleErr
		}
		tidbGroups, err := s.tidbGroupRepo.Query(ctx, &tidbgroup.Filter{
			ClusterIds: []uint64{req.GetClusterId()},
		})
		if err != nil {
			return err
		}

		if err := s.allowlistRepo.Save(ctx, output); err != nil {
			return err
		}

		tidbGroupIDs := make([]string, 0, len(tidbGroups))
		for _, each := range tidbGroups {
			tidbGroupIDs = append(tidbGroupIDs, strconv.FormatUint(each.Id, 10))
		}

		if workFlowErr := s.workflow.RunDagWithBizId(
			ctx,
			common.UpdateVpcPeeringAllowlist,
			strconv.FormatUint(cluster.OrganizationId, 10),
			"0",
			strconv.FormatUint(cluster.ClusterId, 10),
			map[string]string{
				utilworkflow.KeyTraceId: utilworkflow.GetTraceId(ctx),
				common.KeyClusterId:     strconv.FormatUint(cluster.ClusterId, 10),
				common.KeyTidbGroupIds:  strings.Join(tidbGroupIDs, ","),
			},
		); workFlowErr != nil {
			return workFlowErr
		}
		return nil
	}); err != nil {
		return nil, bizerr.WrapError(err, "failed to update byoc vpc peering allowlist setting")
	}

	return output, nil
}

func resolveIPAccessList(req *globalngv1.UpdateVpcPeeringAllowlistSettingRequest) ([]*globalngv1.Endpoint_IpAddress, bizerr.BizError) {
	switch req.GetChange().(type) {
	case *globalngv1.UpdateVpcPeeringAllowlistSettingRequest_Replace:
		return req.GetReplace().GetIpAccessList(), nil
	case *globalngv1.UpdateVpcPeeringAllowlistSettingRequest_Clear:
		return []*globalngv1.Endpoint_IpAddress{}, nil
	default:
		return nil, bizerr.New(bizerr.ErrCodeParams, "change is required")
	}
}
