package vpcpeering

import (
	"context"
	"errors"
	"testing"

	"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"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/consts"
)

type fakeAWSEC2API struct {
	describeVpcPeeringConnectionsOutput *ec2.DescribeVpcPeeringConnectionsOutput
	describeVpcPeeringConnectionsFn     func(context.Context, *ec2.DescribeVpcPeeringConnectionsInput) (*ec2.DescribeVpcPeeringConnectionsOutput, error)
}

func (f *fakeAWSEC2API) DescribeVpcPeeringConnections(
	ctx context.Context, input *ec2.DescribeVpcPeeringConnectionsInput, _ ...func(*ec2.Options),
) (*ec2.DescribeVpcPeeringConnectionsOutput, error) {
	if f.describeVpcPeeringConnectionsFn != nil {
		return f.describeVpcPeeringConnectionsFn(ctx, input)
	}
	return f.describeVpcPeeringConnectionsOutput, nil
}

func TestAWSExternalPeeringQueryer_ListByVPC_NormalizesRequesterAndAccepter(t *testing.T) {
	q := &awsExternalPeeringQueryer{
		ec2Factory: func(VPCContext) (awsEC2API, error) {
			return &fakeAWSEC2API{
				describeVpcPeeringConnectionsOutput: &ec2.DescribeVpcPeeringConnectionsOutput{
					VpcPeeringConnections: []ec2types.VpcPeeringConnection{
						{
							VpcPeeringConnectionId: aws.String("pcx-requester"),
							Status: &ec2types.VpcPeeringConnectionStateReason{
								Code: ec2types.VpcPeeringConnectionStateReasonCodeActive,
							},
							RequesterVpcInfo: &ec2types.VpcPeeringConnectionVpcInfo{
								VpcId:     aws.String("vpc-12345"),
								OwnerId:   aws.String("123456789012"),
								Region:    aws.String("us-west-2"),
								CidrBlock: aws.String("10.0.0.0/16"),
							},
							AccepterVpcInfo: &ec2types.VpcPeeringConnectionVpcInfo{
								VpcId:     aws.String("vpc-dst-1"),
								OwnerId:   aws.String("210987654321"),
								Region:    aws.String("us-east-1"),
								CidrBlock: aws.String("10.1.0.0/16"),
							},
						},
						{
							VpcPeeringConnectionId: aws.String("pcx-accepter"),
							Status: &ec2types.VpcPeeringConnectionStateReason{
								Code: ec2types.VpcPeeringConnectionStateReasonCodeActive,
							},
							RequesterVpcInfo: &ec2types.VpcPeeringConnectionVpcInfo{
								VpcId:     aws.String("vpc-src-2"),
								OwnerId:   aws.String("210987654321"),
								Region:    aws.String("us-east-1"),
								CidrBlock: aws.String("10.2.0.0/16"),
							},
							AccepterVpcInfo: &ec2types.VpcPeeringConnectionVpcInfo{
								VpcId:     aws.String("vpc-12345"),
								OwnerId:   aws.String("123456789012"),
								Region:    aws.String("us-west-2"),
								CidrBlock: aws.String("10.0.0.0/16"),
							},
						},
					},
				},
			}, nil
		},
	}

	got, err := q.ListByVPC(context.Background(), VPCContext{
		Provider:    consts.ProviderTypeEKS,
		Region:      "us-west-2",
		VPCID:       "vpc-12345",
		VPCCIDR:     "10.0.0.0/16",
		AccountID:   "123456789012",
		AccountRole: "regional-server-role",
	})
	require.NoError(t, err)
	require.Len(t, got, 2)
	assert.Equal(t, "vpc-dst-1", got[0].DestinationSide.VpcID)
	assert.Equal(t, "vpc-src-2", got[1].DestinationSide.VpcID)
	assert.Equal(t, "vpc-12345", got[0].CurrentSide.VpcID)
	assert.Equal(t, "vpc-12345", got[1].CurrentSide.VpcID)
}

func TestAWSExternalPeeringQueryer_ListByVPC_FiltersDeletedPeerings(t *testing.T) {
	q := &awsExternalPeeringQueryer{
		ec2Factory: func(VPCContext) (awsEC2API, error) {
			return &fakeAWSEC2API{
				describeVpcPeeringConnectionsOutput: &ec2.DescribeVpcPeeringConnectionsOutput{
					VpcPeeringConnections: []ec2types.VpcPeeringConnection{
						{
							VpcPeeringConnectionId: aws.String("pcx-active"),
							Status: &ec2types.VpcPeeringConnectionStateReason{
								Code: ec2types.VpcPeeringConnectionStateReasonCodeActive,
							},
							RequesterVpcInfo: &ec2types.VpcPeeringConnectionVpcInfo{
								VpcId: aws.String("vpc-12345"),
							},
							AccepterVpcInfo: &ec2types.VpcPeeringConnectionVpcInfo{
								VpcId: aws.String("vpc-dst-1"),
							},
						},
						{
							VpcPeeringConnectionId: aws.String("pcx-deleted"),
							Status: &ec2types.VpcPeeringConnectionStateReason{
								Code: ec2types.VpcPeeringConnectionStateReasonCodeDeleted,
							},
							RequesterVpcInfo: &ec2types.VpcPeeringConnectionVpcInfo{
								VpcId: aws.String("vpc-12345"),
							},
							AccepterVpcInfo: &ec2types.VpcPeeringConnectionVpcInfo{
								VpcId: aws.String("vpc-dst-2"),
							},
						},
					},
				},
			}, nil
		},
	}

	got, err := q.ListByVPC(context.Background(), VPCContext{Provider: consts.ProviderTypeEKS, VPCID: "vpc-12345"})
	require.NoError(t, err)
	require.Len(t, got, 1)
	assert.Equal(t, "pcx-active", got[0].ConnectionID)
}

func TestAWSExternalPeeringQueryer_GetByConnectionID(t *testing.T) {
	q := &awsExternalPeeringQueryer{
		ec2Factory: func(VPCContext) (awsEC2API, error) {
			return &fakeAWSEC2API{
				describeVpcPeeringConnectionsFn: func(context.Context, *ec2.DescribeVpcPeeringConnectionsInput) (*ec2.DescribeVpcPeeringConnectionsOutput, error) {
					return &ec2.DescribeVpcPeeringConnectionsOutput{
						VpcPeeringConnections: []ec2types.VpcPeeringConnection{
							{
								VpcPeeringConnectionId: aws.String("pcx-123"),
								Status: &ec2types.VpcPeeringConnectionStateReason{
									Code:    ec2types.VpcPeeringConnectionStateReasonCodeProvisioning,
									Message: aws.String("creating"),
								},
								RequesterVpcInfo: &ec2types.VpcPeeringConnectionVpcInfo{
									VpcId:     aws.String("vpc-12345"),
									OwnerId:   aws.String("123456789012"),
									Region:    aws.String("us-west-2"),
									CidrBlock: aws.String("10.0.0.0/16"),
								},
								AccepterVpcInfo: &ec2types.VpcPeeringConnectionVpcInfo{
									VpcId:     aws.String("vpc-target"),
									OwnerId:   aws.String("210987654321"),
									Region:    aws.String("us-east-1"),
									CidrBlock: aws.String("10.1.0.0/16"),
								},
							},
						},
					}, nil
				},
			}, nil
		},
	}

	got, err := q.GetByConnectionID(context.Background(), VPCContext{Provider: consts.ProviderTypeEKS, VPCID: "vpc-12345"}, "pcx-123")
	require.NoError(t, err)
	require.NotNil(t, got)
	assert.Equal(t, "pcx-123", got.ConnectionID)
	assert.Equal(t, "provisioning", got.Status)
	assert.Equal(t, "creating", got.Message)
}

func TestAWSExternalPeeringQueryer_GetByConnectionID_DeletedReturnsNil(t *testing.T) {
	q := &awsExternalPeeringQueryer{
		ec2Factory: func(VPCContext) (awsEC2API, error) {
			return &fakeAWSEC2API{
				describeVpcPeeringConnectionsFn: func(context.Context, *ec2.DescribeVpcPeeringConnectionsInput) (*ec2.DescribeVpcPeeringConnectionsOutput, error) {
					return &ec2.DescribeVpcPeeringConnectionsOutput{
						VpcPeeringConnections: []ec2types.VpcPeeringConnection{
							{
								VpcPeeringConnectionId: aws.String("pcx-deleted"),
								Status: &ec2types.VpcPeeringConnectionStateReason{
									Code:    ec2types.VpcPeeringConnectionStateReasonCodeDeleted,
									Message: aws.String("deleted"),
								},
								RequesterVpcInfo: &ec2types.VpcPeeringConnectionVpcInfo{
									VpcId: aws.String("vpc-12345"),
								},
								AccepterVpcInfo: &ec2types.VpcPeeringConnectionVpcInfo{
									VpcId: aws.String("vpc-target"),
								},
							},
						},
					}, nil
				},
			}, nil
		},
	}

	got, err := q.GetByConnectionID(context.Background(), VPCContext{Provider: consts.ProviderTypeEKS, VPCID: "vpc-12345"}, "pcx-deleted")
	require.NoError(t, err)
	assert.Nil(t, got)
}

func TestAWSExternalPeeringQueryer_GetByConnectionID_NotFoundErrorReturnsNil(t *testing.T) {
	q := &awsExternalPeeringQueryer{
		ec2Factory: func(VPCContext) (awsEC2API, error) {
			return &fakeAWSEC2API{
				describeVpcPeeringConnectionsFn: func(context.Context, *ec2.DescribeVpcPeeringConnectionsInput) (*ec2.DescribeVpcPeeringConnectionsOutput, error) {
					return nil, &smithy.GenericAPIError{
						Code:    "InvalidVpcPeeringConnectionID.NotFound",
						Message: "The vpc peering connection ID does not exist",
					}
				},
			}, nil
		},
	}

	got, err := q.GetByConnectionID(context.Background(), VPCContext{Provider: consts.ProviderTypeEKS, VPCID: "vpc-12345"}, "pcx-missing")
	require.NoError(t, err)
	assert.Nil(t, got)
}

func TestAWSExternalPeeringQueryer_GetByConnectionID_NonNotFoundErrorPreserved(t *testing.T) {
	expectedErr := errors.New("throttling")
	q := &awsExternalPeeringQueryer{
		ec2Factory: func(VPCContext) (awsEC2API, error) {
			return &fakeAWSEC2API{
				describeVpcPeeringConnectionsFn: func(context.Context, *ec2.DescribeVpcPeeringConnectionsInput) (*ec2.DescribeVpcPeeringConnectionsOutput, error) {
					return nil, expectedErr
				},
			}, nil
		},
	}

	got, err := q.GetByConnectionID(context.Background(), VPCContext{Provider: consts.ProviderTypeEKS, VPCID: "vpc-12345"}, "pcx-throttle")
	assert.ErrorIs(t, err, expectedErr)
	assert.Nil(t, got)
}
