package sqspoller_test

import (
	"log"
	"sync"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/client"
	"github.com/aws/aws-sdk-go/service/sqs"

	"code.justin.tv/eventbus/sqspoller"
)

func Example(sess client.ConfigProvider) {
	messages := make(chan *sqs.Message, 10)

	client := sqspoller.New(sqspoller.Config{
		Client:     sqs.New(sess),
		Deliver:    messages,
		NumPollers: 2,
		QueueURL:   "http://sqs-queue-url.example.com",
	})

	mySQS := &MySQS{
		client:   client,
		messages: messages,
	}

	// spin up 10 handlers to handle messages
	for i := 0; i < 10; i++ {
		go mySQS.process()
	}

	time.Sleep(10 * time.Minute)
	if mySQS.SemiGracefulShutdown() {
		log.Print("graceful shutdown complete")
	}
}

type MySQS struct {
	client   *sqspoller.SQSPoller
	messages chan *sqs.Message
	wg       sync.WaitGroup
}

func (s *MySQS) process() {
	s.wg.Add(1)
	defer s.wg.Done()
	for message := range s.messages {
		// obviously, do realistic processing work here
		body := aws.StringValue(message.Body)

		if body != "{}" {
			// any non-empty JSON we mark as complete in the client
			s.client.Ack(*message.ReceiptHandle)
		}
	}
}

// SemiGracefulShutdown attempts the best shutdown sequence.
// It will stop the pollers, complete all our own work, then stop the acks.
//
// However, it cancels requests at 10 seconds and sets a hard limit at
// 15 seconds so the application can shut down in a reasonable amount of time.
func (s *MySQS) SemiGracefulShutdown() bool {
	completed := make(chan struct{}, 1)
	go func() {
		s.client.StopPollers() // after this returns, no more messages pulled off queue.

		// now we finish up the work specific to this application.
		close(s.messages)
		s.wg.Wait()

		s.client.StopAcking() // after this returns, all acks have completed
		completed <- struct{}{}
	}()
	// If we are still doing shutdown in 10 sec, cancel all outgoing requests.
	time.AfterFunc(10*time.Second, s.client.CancelOutgoingRequests)

	select {
	case <-completed:
		return true
	case <-time.After(15 * time.Second):
		return false
	}

}
