package s3

import (
	"context"
	"testing"

	"github.com/aws/aws-sdk-go-v2/aws"
)

type mockListObjectVersionsClient struct {
	outputs []*ListObjectVersionsOutput
	inputs  []*ListObjectVersionsInput
	t       *testing.T
}

type mockListMultipartUploadsClient struct {
	outputs []*ListMultipartUploadsOutput
	inputs  []*ListMultipartUploadsInput
	t       *testing.T
}

func (c *mockListObjectVersionsClient) ListObjectVersions(ctx context.Context, input *ListObjectVersionsInput, optFns ...func(*Options)) (*ListObjectVersionsOutput, error) {
	c.inputs = append(c.inputs, input)
	requestCnt := len(c.inputs)
	testCurRequest(len(c.outputs), requestCnt, aws.ToInt32(c.outputs[requestCnt-1].MaxKeys), aws.ToInt32(input.MaxKeys), c.t)
	return c.outputs[requestCnt-1], nil
}

func (c *mockListMultipartUploadsClient) ListMultipartUploads(ctx context.Context, input *ListMultipartUploadsInput, optFns ...func(*Options)) (*ListMultipartUploadsOutput, error) {
	c.inputs = append(c.inputs, input)
	requestCnt := len(c.inputs)
	testCurRequest(len(c.outputs), requestCnt, aws.ToInt32(c.outputs[requestCnt-1].MaxUploads), aws.ToInt32(input.MaxUploads), c.t)
	return c.outputs[requestCnt-1], nil
}

type testCase struct {
	bucket                 *string
	limit                  int32
	requestCnt             int
	stopOnDuplicationToken bool
}

type listOVTestCase struct {
	testCase
	outputs []*ListObjectVersionsOutput
}

type listMPUTestCase struct {
	testCase
	outputs []*ListMultipartUploadsOutput
}

func TestListObjectVersionsPaginator(t *testing.T) {
	cases := map[string]listOVTestCase{
		"page limit 5": {
			testCase: testCase{
				bucket:     aws.String("testBucket1"),
				limit:      5,
				requestCnt: 3,
			},
			outputs: []*ListObjectVersionsOutput{
				{
					NextKeyMarker:       aws.String("testKey1"),
					NextVersionIdMarker: aws.String("testID1"),
					MaxKeys:             aws.Int32(5),
					IsTruncated:         aws.Bool(true),
				},
				{
					NextKeyMarker:       aws.String("testKey2"),
					NextVersionIdMarker: aws.String("testID2"),
					MaxKeys:             aws.Int32(5),
					IsTruncated:         aws.Bool(true),
				},
				{
					NextKeyMarker:       aws.String("testKey3"),
					NextVersionIdMarker: aws.String("testID3"),
					MaxKeys:             aws.Int32(5),
					IsTruncated:         aws.Bool(false),
				},
			},
		},
		"page limit 10 with duplicate marker": {
			testCase: testCase{
				bucket:                 aws.String("testBucket2"),
				limit:                  10,
				requestCnt:             3,
				stopOnDuplicationToken: true,
			},
			outputs: []*ListObjectVersionsOutput{
				{
					NextKeyMarker:       aws.String("testKey1"),
					NextVersionIdMarker: aws.String("testID1"),
					MaxKeys:             aws.Int32(10),
					IsTruncated:         aws.Bool(true),
				},
				{
					NextKeyMarker:       aws.String("testKey2"),
					NextVersionIdMarker: aws.String("testID2"),
					MaxKeys:             aws.Int32(10),
					IsTruncated:         aws.Bool(true),
				},
				{
					NextKeyMarker:       aws.String("testKey2"),
					NextVersionIdMarker: aws.String("testID2"),
					MaxKeys:             aws.Int32(10),
					IsTruncated:         aws.Bool(true),
				},
				{
					NextKeyMarker:       aws.String("testKey3"),
					NextVersionIdMarker: aws.String("testID3"),
					MaxKeys:             aws.Int32(10),
					IsTruncated:         aws.Bool(false),
				},
			},
		},
	}

	for name, c := range cases {
		t.Run(name, func(t *testing.T) {
			client := mockListObjectVersionsClient{
				t:       t,
				outputs: c.outputs,
				inputs:  []*ListObjectVersionsInput{},
			}
			paginator := NewListObjectVersionsPaginator(&client, &ListObjectVersionsInput{
				Bucket: c.bucket,
			}, func(options *ListObjectVersionsPaginatorOptions) {
				options.Limit = c.limit
				options.StopOnDuplicateToken = c.stopOnDuplicationToken
			})

			for paginator.HasMorePages() {
				_, err := paginator.NextPage(context.TODO())
				if err != nil {
					t.Errorf("error: %v", err)
				}
			}

			inputLen := len(client.inputs)
			testTotalRequests(c.requestCnt, inputLen, t)
			for i := 1; i < inputLen; i++ {
				if *client.inputs[i].KeyMarker != *c.outputs[i-1].NextKeyMarker {
					t.Errorf("Expect next input's KeyMarker to be eaqul to %s, got %s",
						*c.outputs[i-1].NextKeyMarker, *client.inputs[i].KeyMarker)
				}
				if *client.inputs[i].VersionIdMarker != *c.outputs[i-1].NextVersionIdMarker {
					t.Errorf("Expect next input's VersionIdMarker to be eaqul to %s, got %s",
						*c.outputs[i-1].NextVersionIdMarker, *client.inputs[i].VersionIdMarker)
				}
			}
		})
	}
}

func TestListMultipartUploadsPaginator(t *testing.T) {
	cases := map[string]listMPUTestCase{
		"page limit 5": {
			testCase: testCase{
				bucket:     aws.String("testBucket1"),
				limit:      5,
				requestCnt: 4,
			},
			outputs: []*ListMultipartUploadsOutput{
				{
					NextKeyMarker:      aws.String("testKey1"),
					NextUploadIdMarker: aws.String("testID1"),
					MaxUploads:         aws.Int32(5),
					IsTruncated:        aws.Bool(true),
				},
				{
					NextKeyMarker:      aws.String("testKey2"),
					NextUploadIdMarker: aws.String("testID2"),
					MaxUploads:         aws.Int32(5),
					IsTruncated:        aws.Bool(true),
				},
				{
					NextKeyMarker:      aws.String("testKey3"),
					NextUploadIdMarker: aws.String("testID3"),
					MaxUploads:         aws.Int32(5),
					IsTruncated:        aws.Bool(true),
				},
				{
					NextKeyMarker:      aws.String("testKey4"),
					NextUploadIdMarker: aws.String("testID4"),
					MaxUploads:         aws.Int32(5),
					IsTruncated:        aws.Bool(false),
				},
			},
		},
		"page limit 10 with duplicate marker": {
			testCase: testCase{
				bucket:                 aws.String("testBucket2"),
				limit:                  10,
				requestCnt:             3,
				stopOnDuplicationToken: true,
			},
			outputs: []*ListMultipartUploadsOutput{
				{
					NextKeyMarker:      aws.String("testKey1"),
					NextUploadIdMarker: aws.String("testID1"),
					MaxUploads:         aws.Int32(10),
					IsTruncated:        aws.Bool(true),
				},
				{
					NextKeyMarker:      aws.String("testKey2"),
					NextUploadIdMarker: aws.String("testID2"),
					MaxUploads:         aws.Int32(10),
					IsTruncated:        aws.Bool(true),
				},
				{
					NextKeyMarker:      aws.String("testKey2"),
					NextUploadIdMarker: aws.String("testID2"),
					MaxUploads:         aws.Int32(10),
					IsTruncated:        aws.Bool(true),
				},
				{
					NextKeyMarker:      aws.String("testKey4"),
					NextUploadIdMarker: aws.String("testID4"),
					MaxUploads:         aws.Int32(10),
					IsTruncated:        aws.Bool(false),
				},
				{
					NextKeyMarker:      aws.String("testKey5"),
					NextUploadIdMarker: aws.String("testID5"),
					MaxUploads:         aws.Int32(10),
					IsTruncated:        aws.Bool(false),
				},
			},
		},
	}

	for name, c := range cases {
		t.Run(name, func(t *testing.T) {
			client := mockListMultipartUploadsClient{
				outputs: c.outputs,
				inputs:  []*ListMultipartUploadsInput{},
				t:       t,
			}
			paginator := NewListMultipartUploadsPaginator(&client, &ListMultipartUploadsInput{
				Bucket: c.bucket,
			}, func(options *ListMultipartUploadsPaginatorOptions) {
				options.Limit = c.limit
				options.StopOnDuplicateToken = c.stopOnDuplicationToken
			})

			for paginator.HasMorePages() {
				_, err := paginator.NextPage(context.TODO())
				if err != nil {
					t.Errorf("error: %v", err)
				}
			}

			inputLen := len(client.inputs)
			testTotalRequests(c.requestCnt, inputLen, t)
			for i := 1; i < inputLen; i++ {
				if *client.inputs[i].KeyMarker != *c.outputs[i-1].NextKeyMarker {
					t.Errorf("Expect next input's KeyMarker to be eaqul to %s, got %s",
						*c.outputs[i-1].NextKeyMarker, *client.inputs[i].KeyMarker)
				}
				if *client.inputs[i].UploadIdMarker != *c.outputs[i-1].NextUploadIdMarker {
					t.Errorf("Expect next input's UploadIdMarker to be eaqul to %s, got %s",
						*c.outputs[i-1].NextUploadIdMarker, *client.inputs[i].UploadIdMarker)
				}
			}
		})
	}
}

func testTotalRequests(expect, actual int, t *testing.T) {
	if actual != expect {
		t.Errorf("Expect total request number to be %d, got %d", expect, actual)
	}
}

func testCurRequest(maxReqCnt, actualReqCnt int, expectLimit, actualLimit int32, t *testing.T) {
	if actualReqCnt > maxReqCnt {
		t.Errorf("Paginator calls client more than expected %d times", maxReqCnt)
	}
	if expectLimit != actualLimit {
		t.Errorf("Expect page limit to be %d, got %d", expectLimit, actualLimit)
	}
}
