package docstore

import (
	"bytes"
	"context"
	"io"
	"os"
	"strings"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/credentials"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"go.uber.org/zap"
)

type S3Store struct {
	logger mlogger.Logger
	client *s3.Client
	bucket string
}

func NewS3Store(logger mlogger.Logger, cfg S3Config) (*S3Store, error) {
	bucket := strings.TrimSpace(cfg.Bucket)
	if bucket == "" {
		return nil, merrors.InvalidArgument("docstore: bucket is required")
	}

	accessKey := strings.TrimSpace(cfg.AccessKey)
	if accessKey == "" && cfg.AccessKeyEnv != "" {
		accessKey = strings.TrimSpace(os.Getenv(cfg.AccessKeyEnv))
	}

	secretKey := strings.TrimSpace(cfg.SecretAccessKey)
	if secretKey == "" && cfg.SecretKeyEnv != "" {
		secretKey = strings.TrimSpace(os.Getenv(cfg.SecretKeyEnv))
	}

	region := strings.TrimSpace(cfg.Region)
	if region == "" {
		region = "us-east-1"
	}

	loadOpts := []func(*config.LoadOptions) error{
		config.WithRegion(region),
	}
	if accessKey != "" || secretKey != "" {
		loadOpts = append(loadOpts, config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
			accessKey,
			secretKey,
			"",
		)))
	}

	endpoint := strings.TrimSpace(cfg.Endpoint)
	if endpoint != "" {
		if !strings.HasPrefix(endpoint, "http://") && !strings.HasPrefix(endpoint, "https://") {
			if cfg.UseSSL {
				endpoint = "https://" + endpoint
			} else {
				endpoint = "http://" + endpoint
			}
		}
	}

	awsCfg, err := config.LoadDefaultConfig(context.Background(), loadOpts...)
	if err != nil {
		logger.Warn("Failed to create AWS config", zap.Error(err), zap.String("bucket", bucket))

		return nil, err
	}

	client := s3.NewFromConfig(awsCfg, func(opts *s3.Options) {
		opts.UsePathStyle = cfg.ForcePathStyle

		if endpoint != "" {
			opts.BaseEndpoint = aws.String(endpoint)
		}
	})

	store := &S3Store{
		logger: logger.Named("docstore").Named("s3"),
		client: client,
		bucket: bucket,
	}
	store.logger.Info("Document storage initialised", zap.String("bucket", bucket), zap.String("endpoint", endpoint))

	return store, nil
}

func (s *S3Store) Save(ctx context.Context, key string, data []byte) error {
	if err := ctx.Err(); err != nil {
		return err
	}

	_, err := s.client.PutObject(ctx, &s3.PutObjectInput{
		Bucket: aws.String(s.bucket),
		Key:    aws.String(key),
		Body:   bytes.NewReader(data),
	})
	if err != nil {
		s.logger.Warn("Failed to upload document", zap.Error(err), zap.String("key", key))

		return err
	}

	return nil
}

func (s *S3Store) Load(ctx context.Context, key string) ([]byte, error) {
	if err := ctx.Err(); err != nil {
		return nil, err
	}

	obj, err := s.client.GetObject(ctx, &s3.GetObjectInput{
		Bucket: aws.String(s.bucket),
		Key:    aws.String(key),
	})
	if err != nil {
		s.logger.Warn("Failed to fetch document", zap.Error(err), zap.String("key", key))

		return nil, err
	}

	defer func() {
		if closeErr := obj.Body.Close(); closeErr != nil {
			s.logger.Warn("Failed to close document body", zap.Error(closeErr), zap.String("key", key))
		}
	}()

	return io.ReadAll(obj.Body)
}

var _ Store = (*S3Store)(nil)
