package files

import (
	"bytes"
	"context"
	"io"
	"net/http"
	"time"

	"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"
)

// FileStorer is the interface satisfied by Store and used for test injection.
type FileStorer interface {
	Upload(ctx context.Context, key string, file io.Reader) (contentType string, bytesWritten int64, err error)
	Delete(ctx context.Context, key string) error
	PresignDownload(ctx context.Context, key string) (string, error)
}

// Store is a thin wrapper around an S3-compatible client.
type Store struct {
	client *s3.Client
	bucket string
}

// NewStore constructs a Store pointed at an S3-compatible endpoint.
//
// endpoint: e.g. "http://localhost:9000" (MinIO) or "https://<account>.r2.cloudflarestorage.com" (R2)
// usePathStyle: true for MinIO (required per Pitfall 1), false for R2
func NewStore(ctx context.Context, endpoint, bucket, region, accessKey, secretKey string, usePathStyle bool) (*Store, error) {
	cfg, err := config.LoadDefaultConfig(ctx,
		config.WithRegion(region),
		config.WithCredentialsProvider(
			credentials.NewStaticCredentialsProvider(accessKey, secretKey, ""),
		),
	)
	if err != nil {
		return nil, err
	}

	client := s3.NewFromConfig(cfg, func(o *s3.Options) {
		o.BaseEndpoint = aws.String(endpoint)
		o.UsePathStyle = usePathStyle // true for MinIO; false or omit for R2
		// Only compute checksums when the server explicitly requires them.
		// Without this, SDK v2 tries to use trailing checksums over HTTP, which
		// requires a seekable stream — incompatible with io.MultiReader (MinIO local dev).
		o.RequestChecksumCalculation = aws.RequestChecksumCalculationWhenRequired
	})

	return &Store{client: client, bucket: bucket}, nil
}

// Upload buffers the file, sniffs its content-type, then PUTs it to S3.
//
// Buffering is required because AWS SDK v2 needs a seekable body to compute
// the SigV4 payload hash over plain HTTP (MinIO dev). The 25 MB cap enforced
// upstream by http.MaxBytesReader makes this safe.
func (s *Store) Upload(ctx context.Context, key string, file io.Reader) (contentType string, bytesWritten int64, err error) {
	buf, err := io.ReadAll(file)
	if err != nil {
		return "", 0, err
	}

	contentType = http.DetectContentType(buf)
	bytesWritten = int64(len(buf))

	_, err = s.client.PutObject(ctx, &s3.PutObjectInput{
		Bucket:        aws.String(s.bucket),
		Key:           aws.String(key),
		Body:          bytes.NewReader(buf),
		ContentType:   aws.String(contentType),
		ContentLength: aws.Int64(bytesWritten),
	})
	return contentType, bytesWritten, err
}

// Delete removes an object from S3.
func (s *Store) Delete(ctx context.Context, key string) error {
	_, err := s.client.DeleteObject(ctx, &s3.DeleteObjectInput{
		Bucket: aws.String(s.bucket),
		Key:    aws.String(key),
	})
	return err
}

// PresignDownload returns a time-limited presigned GET URL (5-minute TTL).
func (s *Store) PresignDownload(ctx context.Context, key string) (string, error) {
	presignClient := s3.NewPresignClient(s.client)
	req, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
		Bucket: aws.String(s.bucket),
		Key:    aws.String(key),
	}, func(o *s3.PresignOptions) {
		o.Expires = 5 * time.Minute
	})
	if err != nil {
		return "", err
	}
	return req.URL, nil
}
