package vpcpeering

import (
	"context"
	"errors"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/service/ec2"
	ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
	smithy "github.com/aws/smithy-go"

	cloudaws "github.com/tidbcloud/infra-provider/regional-server/pkg/cloud/aws"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/consts"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
)

type awsEC2API interface {
	DescribeVpcPeeringConnections(context.Context, *ec2.DescribeVpcPeeringConnectionsInput, ...func(*ec2.Options)) (*ec2.DescribeVpcPeeringConnectionsOutput, error)
}

type awsExternalPeeringQueryer struct {
	ec2Factory func(VPCContext) (awsEC2API, error)
}

func NewAWSExternalPeeringQueryer(awsCli cloudaws.Client) ExternalPeeringQueryer {
	return &awsExternalPeeringQueryer{
		ec2Factory: func(vpcCtx VPCContext) (awsEC2API, error) {
			roleARN := util.GetRoleARN(vpcCtx.AccountID, vpcCtx.AccountRole)
			return awsCli.EC2(&cloudaws.AssumedRole{RoleARN: roleARN}, cloudaws.WithRegion(vpcCtx.Region)), nil
		},
	}
}

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

	outputs := make([]*ec2.DescribeVpcPeeringConnectionsOutput, 0, 2)
	for _, filterName := range []string{"requester-vpc-info.vpc-id", "accepter-vpc-info.vpc-id"} {
		out, describeErr := cli.DescribeVpcPeeringConnections(ctx, &ec2.DescribeVpcPeeringConnectionsInput{
			Filters: []ec2types.Filter{{
				Name:   aws.String(filterName),
				Values: []string{vpcCtx.VPCID},
			}},
		})
		if describeErr != nil {
			return nil, describeErr
		}
		outputs = append(outputs, out)
	}

	results := make([]ExternalPeering, 0)
	seen := make(map[string]struct{})
	for _, out := range outputs {
		for _, item := range out.VpcPeeringConnections {
			peering, ok := normalizeAWSPeering(vpcCtx.VPCID, item)
			if !ok {
				continue
			}
			peering.Provider = consts.ProviderTypeEKS
			if _, exists := seen[peering.ConnectionID]; exists {
				continue
			}
			seen[peering.ConnectionID] = struct{}{}
			results = append(results, *peering)
		}
	}

	return results, nil
}

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

	out, err := cli.DescribeVpcPeeringConnections(ctx, &ec2.DescribeVpcPeeringConnectionsInput{
		VpcPeeringConnectionIds: []string{connectionID},
	})
	if err != nil {
		if isAWSVpcPeeringNotFoundError(err) {
			return nil, nil
		}
		return nil, err
	}
	for _, item := range out.VpcPeeringConnections {
		if peering, ok := normalizeAWSPeering(vpcCtx.VPCID, item); ok {
			peering.Provider = consts.ProviderTypeEKS
			return peering, nil
		}
	}
	return nil, nil
}

func isAWSVpcPeeringNotFoundError(err error) bool {
	var apiErr smithy.APIError
	if !errors.As(err, &apiErr) {
		return false
	}
	return apiErr.ErrorCode() == "InvalidVpcPeeringConnectionID.NotFound"
}

func normalizeAWSPeering(vpcID string, p ec2types.VpcPeeringConnection) (*ExternalPeering, bool) {
	if isDeletedExternalPeeringStatus(string(p.Status.Code)) {
		return nil, false
	}

	requester := normalizeAWSVpcSide(p.RequesterVpcInfo)
	accepter := normalizeAWSVpcSide(p.AccepterVpcInfo)

	peering := &ExternalPeering{
		ConnectionID: aws.ToString(p.VpcPeeringConnectionId),
		Status:       string(p.Status.Code),
		Message:      aws.ToString(p.Status.Message),
	}

	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 normalizeAWSVpcSide(info *ec2types.VpcPeeringConnectionVpcInfo) PeeringSide {
	if info == nil {
		return PeeringSide{}
	}
	return PeeringSide{
		VpcID:     aws.ToString(info.VpcId),
		AccountID: aws.ToString(info.OwnerId),
		Region:    aws.ToString(info.Region),
		CIDR:      aws.ToString(info.CidrBlock),
	}
}
