package s3client

import (
	"net/http"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
)

const (
	MaxRetries               = 5
	MetadataBackupCephRegion = ":default-placement"
)

type S3Client struct {
	s3 *s3.S3
}

func New(accessKey, secretKey []byte, cephEndpoint string) *S3Client {

	creds := credentials.NewStaticCredentials(string(accessKey), string(secretKey), "")

	// FIXME: private cloud does not support setting region temporarily.
	awsConfig := aws.NewConfig().
		WithRegion(MetadataBackupCephRegion).
		WithCredentials(creds).
		WithEndpoint(cephEndpoint).
		WithS3ForcePathStyle(true).
		WithDisableSSL(true).
		WithHTTPClient(&http.Client{Timeout: 3 * time.Second}).
		WithMaxRetries(MaxRetries)

	sess := session.Must(session.NewSession(awsConfig))

	return &S3Client{
		s3: s3.New(sess),
	}
}

func (s *S3Client) GetObject(bucket string, key string) (*s3.GetObjectOutput, error) {
	input := &s3.GetObjectInput{
		Bucket: aws.String(bucket),
		Key:    aws.String(key),
	}

	result, err := s.s3.GetObject(input)
	if err != nil {
		return nil, err
	}

	return result, nil
}

func (s *S3Client) ListObjects(bucket string, maxKeys int64, prefix string) (*s3.ListObjectsOutput, error) {
	// Before listObject, create bucket first
	_, err := s.CreateBucket(bucket)
	if err != nil {
		return nil, err
	}

	input := &s3.ListObjectsInput{
		Bucket:  aws.String(bucket),
		MaxKeys: aws.Int64(maxKeys),
	}

	if prefix != "" {
		input = input.SetPrefix(prefix)
	}

	result, err := s.s3.ListObjects(input)
	if err != nil {
		return nil, err
	}

	return result, nil
}

func (s *S3Client) DeleteObjects(bucket string, identifiers []*s3.ObjectIdentifier) (*s3.DeleteObjectsOutput, error) {
	input := &s3.DeleteObjectsInput{
		Bucket: aws.String(bucket),
		Delete: &s3.Delete{
			Objects: identifiers,
			Quiet:   aws.Bool(false),
		},
	}

	result, err := s.s3.DeleteObjects(input)
	if err != nil {
		return nil, err
	}

	return result, nil
}

func (s *S3Client) CreateBucket(bucket string) (*s3.CreateBucketOutput, error) {
	input := &s3.CreateBucketInput{
		Bucket: aws.String(bucket),
	}

	result, err := s.s3.CreateBucket(input)
	if err != nil {
		if aerr, ok := err.(awserr.Error); !ok ||
			aerr.Code() != s3.ErrCodeBucketAlreadyExists && aerr.Code() != s3.ErrCodeBucketAlreadyOwnedByYou {
			return nil, err
		}
	}

	return result, nil
}
