package jobqueue

import (
	"context"
	"errors"
	"math"
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/sqs"
	gomock "github.com/golang/mock/gomock"
	"github.com/smartystreets/goconvey/convey"
	"github.com/tidbcloud/account/central/jobqueue/mock_jobqueue"
	"github.com/tidbcloud/tiinf/errs"
)

type schema struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func TestJobQueue(t *testing.T) {
	convey.Convey("JobQueue", t, func() {
		ctrl := gomock.NewController(t)
		defer ctrl.Finish()

		jobName := "test_job"
		v := schema{"test", 1}
		mockedJobQueue := mock_jobqueue.NewMockSQSIface(ctrl)
		handler := func(context.Context, []byte) error {
			return nil
		}

		handlerFailed := func(context.Context, []byte) error {
			return errors.New("failed")
		}

		handlerCatchError := func(ctx context.Context, payload []byte) error {
			return CatchError(ctx, func() error {
				return errors.New("catch error")
			})
		}

		handlerCatchPanic := func(ctx context.Context, payload []byte) error {
			return CatchError(ctx, func() error {
				panic("catch panic")
			})
		}

		ctx := context.Background()
		q := JobQueue{}
		q.mapper = make(map[string]Handler)
		q.sqsSvc = mockedJobQueue

		convey.Convey("Enqueue", func() {
			convey.Convey("should return error if v can't marshal", func() {
				err := q.Enqueue(ctx, jobName, math.Inf(1))
				convey.So(err, convey.ShouldNotBeNil)
			})

			convey.Convey("enqueue should works", func() {
				mockedJobQueue.EXPECT().SendMessage(gomock.Any()).Return(nil, nil)
				err := q.Enqueue(ctx, jobName, v)
				convey.So(err, convey.ShouldBeNil)
			})
		})

		convey.Convey("Register", func() {
			convey.Convey("should works", func() {
				err := q.Register(jobName, handler)
				convey.So(err, convey.ShouldBeNil)
			})

			convey.Convey("should return duplicate job error", func() {
				err := q.Register(jobName, handler)
				convey.So(err, convey.ShouldBeNil)

				err = q.Register(jobName, handler)
				convey.So(errs.IsError(err, ErrJobAlreadyRegistered), convey.ShouldBeTrue)
			})
		})

		convey.Convey("handleMsg", func() {
			msg := sqs.Message{ReceiptHandle: aws.String("test")}

			convey.Convey("msg.Body is nil", func() {
				err := q.handleMsg(ctx, &msg)
				convey.So(errs.IsError(err, ErrBadJob), convey.ShouldBeTrue)
			})

			msg.Body = aws.String("{}")
			msg.MessageAttributes = nil
			convey.Convey("message attributes is nil", func() {
				err := q.handleMsg(ctx, &msg)
				convey.So(errs.IsError(err, ErrBadJob), convey.ShouldBeTrue)
			})

			msg.MessageAttributes = map[string]*sqs.MessageAttributeValue{}
			convey.Convey("message attributes does not contain jobName", func() {
				err := q.handleMsg(ctx, &msg)
				convey.So(errs.IsError(err, ErrBadJob), convey.ShouldBeTrue)
			})

			msg.MessageAttributes[jobNameAttr] = &sqs.MessageAttributeValue{
				DataType:    aws.String("String"),
				StringValue: aws.String(jobName),
			}
			convey.Convey("handler not registered", func() {
				err := q.handleMsg(ctx, &msg)
				convey.So(errs.IsError(err, ErrJobHandlerNotFound), convey.ShouldBeTrue)
			})

			convey.Convey("handler works", func() {
				q.mapper[jobName] = handler
				mockedJobQueue.EXPECT().DeleteMessage(gomock.Any()).Return(nil, nil)
				err := q.handleMsg(ctx, &msg)
				convey.So(err, convey.ShouldBeNil)
			})

			convey.Convey("handler failed", func() {
				q.mapper[jobName] = handlerFailed
				mockedJobQueue.EXPECT().DeleteMessage(gomock.Any()).Times(0)
				err := q.handleMsg(ctx, &msg)
				convey.So(err, convey.ShouldNotBeNil)
			})

			convey.Convey("catch error", func() {
				q.mapper[jobName] = handlerCatchError
				mockedJobQueue.EXPECT().DeleteMessage(gomock.Any()).Times(1)
				err := q.handleMsg(ctx, &msg)
				convey.So(err, convey.ShouldBeNil)
			})

			convey.Convey("catch panic", func() {
				q.mapper[jobName] = handlerCatchPanic
				mockedJobQueue.EXPECT().DeleteMessage(gomock.Any()).Times(1)
				err := q.handleMsg(ctx, &msg)
				convey.So(err, convey.ShouldBeNil)
			})
		})
	})
}
