package objectstore

import (
	"context"
	"io"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3manager"
	"gitlab.com/gitlab-org/labkit/log"

	"gitlab.com/gitlab-org/gitlab-workhorse/internal/config"
)

type S3Object struct {
	credentials config.S3Credentials
	config      config.S3Config
	objectName  string
	uploaded    bool

	*uploader
}

func NewS3Object(objectName string, s3Credentials config.S3Credentials, s3Config config.S3Config) (*S3Object, error) {
	o := &S3Object{
		credentials: s3Credentials,
		config:      s3Config,
		objectName:  objectName,
	}

	o.uploader = newUploader(o)
	return o, nil
}

func setEncryptionOptions(input *s3manager.UploadInput, s3Config config.S3Config) {
	if s3Config.ServerSideEncryption != "" {
		input.ServerSideEncryption = aws.String(s3Config.ServerSideEncryption)

		if s3Config.ServerSideEncryption == s3.ServerSideEncryptionAwsKms && s3Config.SSEKMSKeyID != "" {
			input.SSEKMSKeyId = aws.String(s3Config.SSEKMSKeyID)
		}
	}
}

func (s *S3Object) Upload(ctx context.Context, r io.Reader) error {
	sess, err := setupS3Session(s.credentials, s.config)
	if err != nil {
		log.WithError(err).Error("error creating S3 session")
		return err
	}

	uploader := s3manager.NewUploader(sess)

	input := &s3manager.UploadInput{
		Bucket: aws.String(s.config.Bucket),
		Key:    aws.String(s.objectName),
		Body:   r,
	}

	setEncryptionOptions(input, s.config)

	_, err = uploader.UploadWithContext(ctx, input)
	if err != nil {
		log.WithError(err).Error("error uploading S3 session")
		return err
	}

	s.uploaded = true

	return nil
}

func (s *S3Object) ETag() string {
	return ""
}

func (s *S3Object) Abort() {
	s.Delete()
}

func (s *S3Object) Delete() {
	if !s.uploaded {
		return
	}

	session, err := setupS3Session(s.credentials, s.config)
	if err != nil {
		log.WithError(err).Error("error setting up S3 session in delete")
		return
	}

	svc := s3.New(session)
	input := &s3.DeleteObjectInput{
		Bucket: aws.String(s.config.Bucket),
		Key:    aws.String(s.objectName),
	}

	// Note we can't use the request context because in a successful
	// case, the original request has already completed.
	deleteCtx, cancel := context.WithTimeout(context.Background(), 60*time.Second) // lint:allow context.Background
	defer cancel()

	_, err = svc.DeleteObjectWithContext(deleteCtx, input)
	if err != nil {
		log.WithError(err).Error("error deleting S3 object", err)
	}
}
