package vpcpeering

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	sdkerrors "github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
	"github.com/aliyun/alibaba-cloud-sdk-go/services/vpcpeer"

	aliyuncloud "github.com/tidbcloud/infra-provider/regional-server/pkg/cloud/aliyun"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/consts"
)

type aliyunVpcPeerAPI interface {
	ListVpcPeerConnections(*vpcpeer.ListVpcPeerConnectionsRequest) (*vpcpeer.ListVpcPeerConnectionsResponse, error)
	GetVpcPeerConnectionAttribute(*vpcpeer.GetVpcPeerConnectionAttributeRequest) (*vpcpeer.GetVpcPeerConnectionAttributeResponse, error)
}

type aliyunExternalPeeringQueryer struct {
	peerFactory func(VPCContext) (aliyunVpcPeerAPI, error)
}

func NewAliyunExternalPeeringQueryer(aliCli aliyuncloud.Client) ExternalPeeringQueryer {
	return &aliyunExternalPeeringQueryer{
		peerFactory: func(vpcCtx VPCContext) (aliyunVpcPeerAPI, error) {
			return aliCli.VpcPeer(&aliyuncloud.AssumedRole{
				RoleARN:    fmt.Sprintf("acs:ram::%s:role/%s", vpcCtx.AccountID, vpcCtx.AccountRole),
				ExternalID: nil,
			}, aliyuncloud.WithRegion(vpcCtx.Region))
		},
	}
}

func (q *aliyunExternalPeeringQueryer) ListByVPC(_ context.Context, vpcCtx VPCContext) ([]ExternalPeering, error) {
	cli, err := q.peerFactory(vpcCtx)
	if err != nil {
		return nil, err
	}

	req := vpcpeer.CreateListVpcPeerConnectionsRequest()
	req.VpcId = &[]string{vpcCtx.VPCID}
	resp, err := cli.ListVpcPeerConnections(req)
	if err != nil {
		return nil, err
	}

	results := make([]ExternalPeering, 0, len(resp.VpcPeerConnects))
	for _, item := range resp.VpcPeerConnects {
		peering, ok := normalizeAliyunPeering(vpcCtx.VPCID, item)
		if !ok {
			continue
		}
		peering.Provider = consts.ProviderTypeACK
		results = append(results, *peering)
	}
	return results, nil
}

func (q *aliyunExternalPeeringQueryer) GetByConnectionID(_ context.Context, vpcCtx VPCContext, connectionID string) (*ExternalPeering, error) {
	cli, err := q.peerFactory(vpcCtx)
	if err != nil {
		return nil, err
	}

	req := vpcpeer.CreateGetVpcPeerConnectionAttributeRequest()
	req.InstanceId = connectionID
	resp, err := cli.GetVpcPeerConnectionAttribute(req)
	if err != nil {
		if isAliyunVpcPeeringNotFoundError(err) {
			return nil, nil
		}
		return nil, err
	}
	peering, ok := normalizeAliyunAttribute(vpcCtx.VPCID, resp)
	if !ok {
		return nil, nil
	}
	peering.Provider = consts.ProviderTypeACK
	return peering, nil
}

func normalizeAliyunPeering(vpcID string, item vpcpeer.VpcPeerConnectsItem) (*ExternalPeering, bool) {
	if isDeletedExternalPeeringStatus(item.Status) {
		return nil, false
	}

	requester := normalizeAliyunPeerSide(item.Vpc, item.OwnerId, item.RegionId)
	accepter := normalizeAliyunAcceptingSide(item.AcceptingVpc, item.AcceptingOwnerUid, item.AcceptingRegionId)

	peering := &ExternalPeering{
		ConnectionID: item.InstanceId,
		Status:       strings.ToLower(item.Status),
	}
	switch vpcID {
	case requester.VpcID:
		peering.CurrentSide = requester
		peering.DestinationSide = accepter
	case accepter.VpcID:
		peering.CurrentSide = accepter
		peering.DestinationSide = requester
	default:
		return nil, false
	}
	return peering, true
}

func normalizeAliyunAttribute(vpcID string, item *vpcpeer.GetVpcPeerConnectionAttributeResponse) (*ExternalPeering, bool) {
	if item == nil {
		return nil, false
	}
	if isDeletedExternalPeeringStatus(item.Status) {
		return nil, false
	}

	requester := normalizeAliyunPeerSide(item.Vpc, item.OwnerId, item.RegionId)
	accepter := normalizeAliyunAcceptingSide(item.AcceptingVpc, item.AcceptingOwnerUid, item.AcceptingRegionId)

	peering := &ExternalPeering{
		ConnectionID: item.InstanceId,
		Status:       strings.ToLower(item.Status),
	}
	switch vpcID {
	case requester.VpcID:
		peering.CurrentSide = requester
		peering.DestinationSide = accepter
	case accepter.VpcID:
		peering.CurrentSide = accepter
		peering.DestinationSide = requester
	default:
		return nil, false
	}
	return peering, true
}

func normalizeAliyunPeerSide(vpc vpcpeer.Vpc, ownerID int64, region string) PeeringSide {
	return PeeringSide{
		VpcID:     vpc.VpcId,
		AccountID: strconv.FormatInt(ownerID, 10),
		Region:    region,
		CIDR:      firstCIDR(vpc.Ipv4Cidrs),
	}
}

func normalizeAliyunAcceptingSide(vpc vpcpeer.AcceptingVpc, ownerID int64, region string) PeeringSide {
	return PeeringSide{
		VpcID:     vpc.VpcId,
		AccountID: strconv.FormatInt(ownerID, 10),
		Region:    region,
		CIDR:      firstCIDR(vpc.Ipv4Cidrs),
	}
}

func firstCIDR(cidrs []string) string {
	// The current protobuf surface only carries one CIDR string, so keep the
	// primary block until the API can expose multi-CIDR peerings explicitly.
	if len(cidrs) == 0 {
		return ""
	}
	return cidrs[0]
}

func isAliyunVpcPeeringNotFoundError(err error) bool {
	apiErr, ok := err.(sdkerrors.Error)
	if !ok {
		return false
	}
	switch apiErr.ErrorCode() {
	case "ResourceNotFound.InstanceId", "ResourceNotFound.VpcPeer", "InvalidInstanceIds.NotFound":
		return true
	default:
		return false
	}
}
