package storage

import (
	"context"
	"errors"
	"fmt"
	"io"
	"strings"

	awsconfig "github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"github.com/aws/aws-sdk-go-v2/service/s3/types"
)

type S3 struct {
	bucket string
	prefix string
	client *s3.Client
}

func NewS3(ctx context.Context, bucket, prefix string) (*S3, error) {
	if bucket == "" {
		return nil, fmt.Errorf("storage: s3 backend requires a bucket")
	}
	sdkConfig, err := awsconfig.LoadDefaultConfig(ctx)
	if err != nil {
		return nil, fmt.Errorf("storage: load s3 config: %w", err)
	}
	client := s3.NewFromConfig(sdkConfig)
	versioning, err := client.GetBucketVersioning(ctx, &s3.GetBucketVersioningInput{
		Bucket: &bucket,
	})
	if err != nil {
		return nil, fmt.Errorf("storage: check s3 bucket versioning: %w", err)
	}
	if versioning.Status != "" {
		return nil, fmt.Errorf("storage: s3 cache bucket must not use versioning")
	}
	return &S3{
		bucket: bucket,
		prefix: strings.Trim(prefix, "/"),
		client: client,
	}, nil
}

func (s *S3) fullKey(key string) (string, error) {
	if err := ValidateKey(key); err != nil {
		return "", err
	}
	if s.prefix == "" {
		return key, nil
	}
	return s.prefix + "/" + key, nil
}

func (s *S3) Get(ctx context.Context, key string) (io.ReadCloser, error) {
	full, err := s.fullKey(key)
	if err != nil {
		return nil, err
	}
	res, err := s.client.GetObject(ctx, &s3.GetObjectInput{
		Bucket: &s.bucket,
		Key:    &full,
	})
	if err != nil {
		var nsk *types.NoSuchKey
		if errors.As(err, &nsk) {
			return nil, ErrNotExist
		}
		return nil, fmt.Errorf("storage: get %q: %w", key, err)
	}
	return res.Body, nil
}

func (s *S3) Put(ctx context.Context, key string, r io.Reader) error {
	full, err := s.fullKey(key)
	if err != nil {
		return err
	}
	if _, err := s.client.PutObject(ctx, &s3.PutObjectInput{
		Bucket: &s.bucket,
		Key:    &full,
		Body:   r,
	}); err != nil {
		return fmt.Errorf("storage: put %q: %w", key, err)
	}
	return nil
}

func (s *S3) Delete(ctx context.Context, key string) error {
	full, err := s.fullKey(key)
	if err != nil {
		return err
	}
	if _, err := s.client.DeleteObject(ctx, &s3.DeleteObjectInput{
		Bucket: &s.bucket,
		Key:    &full,
	}); err != nil {
		return fmt.Errorf("storage: delete %q: %w", key, err)
	}
	return nil
}
