package main

import (
	"context"
	"encoding/json"
	"errors"
	"os"
	"testing"
	"time"

	"code.justin.tv/foundation/history-service/internal/awsmocks"
	"code.justin.tv/foundation/history-service/internal/gdpr"
	"code.justin.tv/foundation/history-service/internal/gdpr/gdprmocks"
	"code.justin.tv/foundation/history-service/internal/queue"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"github.com/aws/aws-sdk-go/service/sqs"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/suite"
)

type GenerateReportSuite struct {
	suite.Suite
	mockedGdprClient *gdprmocks.ClientAPI
	mockedQueue      *awsmocks.SQSAPI
	mockedDDB        *awsmocks.DynamoDBAPI
	generateReport   *generateReport
}

func (s *GenerateReportSuite) SetupTest() {

	s.mockedGdprClient = new(gdprmocks.ClientAPI)
	s.mockedQueue = new(awsmocks.SQSAPI)
	s.mockedDDB = new(awsmocks.DynamoDBAPI)

	s.generateReport = &generateReport{
		ddb:                   s.mockedDDB,
		reportStatusTableName: "fake-table-name",
		queue:                 s.mockedQueue,
		queueURL:              "fakeQueueURL",
		gdprClient:            s.mockedGdprClient,
	}
}
func (s *GenerateReportSuite) TeardownTest() {
	s.mockedGdprClient.AssertExpectations(s.T())
	s.mockedDDB.AssertExpectations(s.T())
	s.mockedQueue.AssertExpectations(s.T())
}

func (s *GenerateReportSuite) TestGenerateReportNoMessage() {
	ctx := context.Background()
	s.mockedQueue.On("ReceiveMessageWithContext", ctx, &sqs.ReceiveMessageInput{
		QueueUrl:            aws.String(s.generateReport.queueURL),
		MaxNumberOfMessages: aws.Int64(1),
		WaitTimeSeconds:     aws.Int64(20),
	}).Return(&sqs.ReceiveMessageOutput{}, nil)

	err := s.generateReport.GenerateWithContext(ctx)
	s.Assert().NoError(err)
}

func (s *GenerateReportSuite) TestGenerateReportQueueError() {
	ctx := context.Background()
	s.mockedQueue.On("ReceiveMessageWithContext", ctx, &sqs.ReceiveMessageInput{
		QueueUrl:            aws.String(s.generateReport.queueURL),
		MaxNumberOfMessages: aws.Int64(1),
		WaitTimeSeconds:     aws.Int64(20),
	}).Return(nil, errors.New("SomeError"))

	err := s.generateReport.GenerateWithContext(ctx)
	s.Assert().Error(err)
}

func (s *GenerateReportSuite) TestGenerateReportGDPRError() {
	ctx := context.Background()
	s.mockedQueue.On("ReceiveMessageWithContext", ctx, &sqs.ReceiveMessageInput{
		QueueUrl:            aws.String(s.generateReport.queueURL),
		MaxNumberOfMessages: aws.Int64(1),
		WaitTimeSeconds:     aws.Int64(20),
	}).Return(s.buildFakeQueueMessage("123"), nil)

	s.mockedGdprClient.On("GenerateUserReport", ctx, "123").Return(nil, errors.New("fake errors from gdpr lib"))
	err := s.generateReport.GenerateWithContext(ctx)
	s.Assert().Error(err)
}

func (s *GenerateReportSuite) TestGenerateReportValidMessagePresignedURL() {
	ctx := context.Background()
	err := os.Setenv("ECS_CONTAINER_METADATA_URI", "https://git-aws.internal.justin.tv/health")
	s.Require().NoError(err)
	s.mockedQueue.On("ReceiveMessageWithContext", ctx, &sqs.ReceiveMessageInput{
		QueueUrl:            aws.String(s.generateReport.queueURL),
		MaxNumberOfMessages: aws.Int64(1),
		WaitTimeSeconds:     aws.Int64(20),
	}).Return(s.buildFakeQueueMessage("123"), nil).Once()

	s.mockedQueue.On("ReceiveMessageWithContext", ctx, mock.Anything).Return(&sqs.ReceiveMessageOutput{Messages: []*sqs.Message{}}, nil).Once()

	s.mockedGdprClient.On("GenerateUserReport", ctx, "123").Return(
		&gdpr.GenerateReportOutput{
			UserID:     "123",
			Timestamp:  time.Now(),
			Expiration: time.Now().Add(1 * time.Minute),
			Key:        "some-key",
			Bucket:     "bucket",
		},
		nil,
	).Once()

	s.mockedDDB.On("UpdateItemWithContext", ctx, mock.Anything).Run(func(args mock.Arguments) {
		arg := args.Get(1).(*dynamodb.UpdateItemInput)
		// build update expression should have all 4 attribute
		expectedUpdateExpression := "SET #0 = :0, #1 = :1, #2 = :2, #3 = :3, #4 = :4, #5 = :5\n"
		s.Assert().Equal(expectedUpdateExpression, aws.StringValue(arg.UpdateExpression))
		s.Assert().Equal(6, len(arg.ExpressionAttributeValues))
		s.Assert().Equal(6, len(arg.ExpressionAttributeNames))
	}).Return(&dynamodb.UpdateItemOutput{}, nil).Once()

	s.mockedQueue.On("DeleteMessageWithContext", ctx, mock.Anything).Return(&sqs.DeleteMessageOutput{}, nil).Once()
	err = s.generateReport.GenerateWithContext(ctx)
	s.Assert().NoError(err)
}

func (s *GenerateReportSuite) TestGenerateReportValidMessage() {
	ctx := context.Background()
	err := os.Setenv("ECS_CONTAINER_METADATA_URI", "https://git-aws.internal.justin.tv/health")
	s.Require().NoError(err)
	s.mockedQueue.On("ReceiveMessageWithContext", ctx, &sqs.ReceiveMessageInput{
		QueueUrl:            aws.String(s.generateReport.queueURL),
		MaxNumberOfMessages: aws.Int64(1),
		WaitTimeSeconds:     aws.Int64(20),
	}).Return(s.buildFakeQueueMessage("123"), nil).Once()

	s.mockedQueue.On("ReceiveMessageWithContext", ctx, mock.Anything).Return(&sqs.ReceiveMessageOutput{Messages: []*sqs.Message{}}, nil).Once()

	s.mockedGdprClient.On("GenerateUserReport", ctx, "123").Return(
		&gdpr.GenerateReportOutput{
			UserID:     "123",
			Timestamp:  time.Now(),
			Expiration: time.Now().Add(1 * time.Minute),
			Key:        "test",
			Bucket:     "location",
		},
		nil,
	).Once()

	s.mockedDDB.On("UpdateItemWithContext", ctx, mock.Anything).Run(func(args mock.Arguments) {
		arg := args.Get(1).(*dynamodb.UpdateItemInput)
		// build update expression should have all 4 attribute
		expectedUpdateExpression := "SET #0 = :0, #1 = :1, #2 = :2, #3 = :3, #4 = :4, #5 = :5\n"
		s.Assert().Equal(expectedUpdateExpression, aws.StringValue(arg.UpdateExpression))
		s.Assert().Equal(6, len(arg.ExpressionAttributeValues))
		s.Assert().Equal(6, len(arg.ExpressionAttributeNames))
	}).Return(&dynamodb.UpdateItemOutput{}, nil).Once()

	s.mockedQueue.On("DeleteMessageWithContext", ctx, mock.Anything).Return(&sqs.DeleteMessageOutput{}, nil).Once()
	err = s.generateReport.GenerateWithContext(ctx)
	s.Assert().NoError(err)
}

func (s *GenerateReportSuite) TestGenerateReportMultipleValidMessage() {
	ctx := context.Background()
	output := s.buildFakeQueueMessage("123")
	err := os.Setenv("ECS_CONTAINER_METADATA_URI", "https://git-aws.internal.justin.tv/health")
	s.Require().NoError(err)
	s.mockedQueue.On("ReceiveMessageWithContext", ctx, &sqs.ReceiveMessageInput{
		QueueUrl:            aws.String(s.generateReport.queueURL),
		MaxNumberOfMessages: aws.Int64(1),
		WaitTimeSeconds:     aws.Int64(20),
	}).
		Return(output, nil).
		Return(output, nil).
		Return(output, nil).
		Return(output, nil).
		Return(&sqs.ReceiveMessageOutput{}, nil)

	s.mockedGdprClient.On("GenerateUserReport", ctx, "123").Return(
		&gdpr.GenerateReportOutput{
			UserID:    "123",
			Timestamp: time.Now(),
			Key:       "key",
			Bucket:    "bucket",
		},
		nil,
	).Times(5)

	s.mockedDDB.On("UpdateItemWithContext", ctx, mock.Anything).Return(&dynamodb.UpdateItemOutput{}, nil).Times(5)
	s.mockedQueue.On("DeleteMessageWithContext", ctx, mock.Anything).Return(&sqs.DeleteMessageOutput{}, nil).Times(5)
	err = s.generateReport.GenerateWithContext(ctx)
	s.Assert().NoError(err)
}

func (s *GenerateReportSuite) buildFakeQueueMessage(userID string) *sqs.ReceiveMessageOutput {
	queueMessage := queue.GenerateUserReportMessage{
		UserID:    userID,
		Timestamp: time.Now(),
	}
	m, err := json.Marshal(queueMessage)
	if err != nil {
		s.Require().NoError(err)
	}

	return &sqs.ReceiveMessageOutput{
		Messages: []*sqs.Message{
			{
				Body:          aws.String(string(m)),
				ReceiptHandle: aws.String("handle"),
			},
		},
	}
}

func TestGenerateReport(t *testing.T) {
	suite.Run(t, new(GenerateReportSuite))
}
