package eventbus

import (
	"context"
	"time"

	telemetry "code.justin.tv/amzn/TwitchTelemetry"

	"code.justin.tv/creator-collab/log/errors"

	eventbus "code.justin.tv/eventbus/client"
	"code.justin.tv/eventbus/client/subscriber/sqsclient"
	"code.justin.tv/eventbus/schema/pkg/user"
	"github.com/aws/aws-sdk-go/aws/session"
)

// Interface that defines all the callbacks that this event queue object
// supports. These should be implemented by the Logic interface.
type QueueHandler interface {
	OnUserDestroyEventReceived(ctx context.Context, userID string) error
}

type Client interface {
	Shutdown() error
}

type eventBusQueueImpl struct {
	queueURL       string
	handler        QueueHandler
	sqsClient      *sqsclient.SQSClient
	sampleReporter *telemetry.SampleReporter
}

/// Exported Functions ///
func CreateClient(queueURL string, awsSession *session.Session, handler QueueHandler, sampleReporter *telemetry.SampleReporter) (Client, error) {
	var queue = &eventBusQueueImpl{
		queueURL:       queueURL,
		handler:        handler,
		sampleReporter: sampleReporter,
	}

	err := queue.connect(queueURL, awsSession)
	if err != nil {
		return nil, err
	}

	return queue, nil
}

func (T *eventBusQueueImpl) Shutdown() error {
	if T.sqsClient == nil {
		return errors.New("Attempted to shutdown the EventBus client with an invalid SQSClient")
	}

	err := T.sqsClient.Shutdown()
	if err != nil {
		return errors.Wrap(err, "Failed to shutdown the eventbus client")
	}

	return nil
}

/// Private Functions ///
func (T *eventBusQueueImpl) connect(queueURL string, awsSession *session.Session) error {
	mux := eventbus.NewMux()
	user.RegisterDestroyHandler(mux, T.userDestroyHandler)

	client, err := sqsclient.New(sqsclient.Config{
		Session:    awsSession,
		Dispatcher: mux.Dispatcher(),
		QueueURL:   queueURL,
	})
	if err != nil {
		return errors.Wrap(err, "Failed to create SQSClient")
	}

	T.sqsClient = client
	return nil
}

func (T *eventBusQueueImpl) userDestroyHandler(ctx context.Context, h *eventbus.Header, event *user.Destroy) error {
	ctx = telemetry.ContextWithOperationName(ctx, "HandleUserDestroyEvent")

	startTime := time.Now()
	err := T.handler.OnUserDestroyEventReceived(ctx, event.UserId)
	duration := time.Since(startTime)

	if err != nil {
		T.sendHandlerTelemetryEvent(ctx, false, duration)
		return errors.Wrap(err, "UserDestory handler failed", errors.Fields{
			"userID": event.UserId,
		})
	}

	T.sendHandlerTelemetryEvent(ctx, true, duration)
	return nil
}

func (T *eventBusQueueImpl) sendHandlerTelemetryEvent(ctx context.Context, success bool, duration time.Duration) {
	reporter := telemetry.SampleReporterWithContext(*T.sampleReporter, ctx)

	// Determine the availability code
	var availabilityCode telemetry.AvailabilityCode
	if success {
		availabilityCode = telemetry.AvailabilityCodeSucccess
	} else {
		availabilityCode = telemetry.AvailabilityCodeServerError
	}

	reporter.ReportAvailabilitySamples(availabilityCode)
	reporter.ReportDurationSample(telemetry.MetricDuration, duration)
}

type NoopClient struct{}

// NoopClient implements the Client interface.
var _ Client = &NoopClient{}

func NewNoopClient() *NoopClient {
	return &NoopClient{}
}

func (c *NoopClient) Shutdown() error {
	return nil
}
