package routing

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"

	"code.justin.tv/eventbus/controlplane/internal/db"
	"code.justin.tv/eventbus/controlplane/internal/environment"
	"code.justin.tv/eventbus/controlplane/internal/logger"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/pkg/errors"
	"go.uber.org/zap"
)

type ConfigActions struct {
	S3 S3Iface
}

func (rca *ConfigActions) PutEventStreamConfigFile(ctx context.Context, eventStream db.EventStream) error {

	buf, err := json.Marshal(Config{
		Arn: eventStream.SNSDetails.SNSTopicARN,
	})
	if err != nil {
		return err
	}

	err = rca.putFileIfChanged(ctx, ConfigPath(eventStream.EventType.Name, eventStream.Environment), buf)
	if err != nil {
		return err
	}
	return nil
}

func (rca *ConfigActions) putFileIfChanged(ctx context.Context, path string, content []byte) error {
	log := logger.FromContext(ctx).With(zap.String("path", path))

	env := environment.Environment()
	obj, err := rca.S3.GetObjectWithContext(ctx, &s3.GetObjectInput{
		Bucket: aws.String(S3BucketName(env)),
		Key:    aws.String(path),
	})

	if err == nil {
		var buf bytes.Buffer
		if _, err = io.Copy(&buf, obj.Body); err != nil {
			return errors.Wrap(err, "failed to copy s3 object body into buffer")
		}

		obj.Body.Close()
		if bytes.Equal(buf.Bytes(), content) {
			return nil
		}
	} else {
		errCode := awsErrorCode(err)
		if errCode != s3CodeNotFound && errCode != s3CodeNoSuchKey {
			return errors.Wrapf(err, "unknown code %q when fetching key %q", errCode, path)
		}
	}

	// If we reach here, either the config file was not found or the content didn't match.

	_, err = rca.S3.PutObjectWithContext(ctx, &s3.PutObjectInput{
		ACL:    aws.String(s3.ObjectCannedACLPublicRead),
		Bucket: aws.String(S3BucketName(env)),
		Key:    aws.String(path),
		Body:   bytes.NewReader(content),

		// Use application/json so browsers handle this nicer.
		// https://stackoverflow.com/questions/477816/what-is-the-correct-json-content-type
		ContentType: aws.String("application/json"),
	})

	if err != nil {
		log.Error("Failed to put config file to s3", zap.Error(err))
		return errors.Wrapf(err, "could not put config to s3 at path %q", path)
	}

	log.Info("Put config file to s3")
	return nil
}

type Config struct {
	Arn string `json:"arn"`
}

// Returns the path (not including domain) of a route config JSON file in S3.
func ConfigPath(eventType string, environment string) string {
	return fmt.Sprintf(routeConfigFormat, eventType, environment)
}

func awsErrorCode(err error) string {
	if e, ok := err.(awserr.Error); ok {
		return e.Code()
	}
	return ""
}
