package services

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"net/url"
	"strings"

	"github.com/minio/minio-go/v7"
	"github.com/minio/minio-go/v7/pkg/credentials"
)

type s3StorageBackend struct {
	cfg    StorageBackendConfig
	client *minio.Client
}

func newS3StorageBackend(cfg StorageBackendConfig) (*s3StorageBackend, error) {
	endpoint := normalizeS3Endpoint(cfg.Endpoint)
	client, err := minio.New(endpoint, &minio.Options{
		Creds:        credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, ""),
		Secure:       cfg.UseSSL,
		Region:       cfg.Region,
		BucketLookup: s3BucketLookup(cfg.PathStyle),
	})
	if err != nil {
		return nil, err
	}
	return &s3StorageBackend{cfg: cfg, client: client}, nil
}

func (b *s3StorageBackend) ID() string   { return b.cfg.ID }
func (b *s3StorageBackend) Type() string { return StorageBackendS3 }

func (b *s3StorageBackend) Put(ctx context.Context, key string, body io.Reader, size int64, contentType string) error {
	cleanKey := cleanObjectKey(key)
	opts := minio.PutObjectOptions{ContentType: contentType}
	_, err := b.client.PutObject(ctx, b.cfg.Bucket, cleanKey, body, size, opts)
	if err != nil {
		return fmt.Errorf("s3 put object %q in bucket %q failed: %w", cleanKey, b.cfg.Bucket, err)
	}
	return nil
}

func (b *s3StorageBackend) Get(ctx context.Context, key string) (StorageObject, error) {
	cleanKey := cleanObjectKey(key)
	object, err := b.client.GetObject(ctx, b.cfg.Bucket, cleanKey, minio.GetObjectOptions{})
	if err != nil {
		return StorageObject{}, fmt.Errorf("s3 get object %q from bucket %q failed: %w", cleanKey, b.cfg.Bucket, err)
	}
	info, err := object.Stat()
	if err != nil {
		object.Close()
		return StorageObject{}, fmt.Errorf("s3 stat object %q in bucket %q failed: %w", cleanKey, b.cfg.Bucket, err)
	}
	return StorageObject{Key: key, Size: info.Size, ContentType: info.ContentType, ModTime: info.LastModified, Body: object}, nil
}

func (b *s3StorageBackend) Delete(ctx context.Context, key string) error {
	cleanKey := cleanObjectKey(key)
	if err := b.client.RemoveObject(ctx, b.cfg.Bucket, cleanKey, minio.RemoveObjectOptions{}); err != nil {
		return fmt.Errorf("s3 delete object %q from bucket %q failed: %w", cleanKey, b.cfg.Bucket, err)
	}
	return nil
}

func (b *s3StorageBackend) DeletePrefix(ctx context.Context, prefix string) error {
	prefix = strings.TrimSuffix(cleanObjectKey(prefix), "/") + "/"
	objects := b.client.ListObjects(ctx, b.cfg.Bucket, minio.ListObjectsOptions{Prefix: prefix, Recursive: true})
	for object := range objects {
		if object.Err != nil {
			return fmt.Errorf("s3 list prefix %q in bucket %q failed: %w", prefix, b.cfg.Bucket, object.Err)
		}
		if err := b.Delete(ctx, object.Key); err != nil {
			return err
		}
	}
	return nil
}

func (b *s3StorageBackend) Usage(ctx context.Context) (int64, error) {
	var total int64
	for object := range b.client.ListObjects(ctx, b.cfg.Bucket, minio.ListObjectsOptions{Recursive: true}) {
		if object.Err != nil {
			return 0, fmt.Errorf("s3 usage list bucket %q failed: %w", b.cfg.Bucket, object.Err)
		}
		total += object.Size
	}
	return total, nil
}

func (b *s3StorageBackend) Test(ctx context.Context) error {
	exists, err := b.client.BucketExists(ctx, b.cfg.Bucket)
	if err != nil {
		return fmt.Errorf("s3 bucket check for %q failed: %w", b.cfg.Bucket, err)
	}
	if !exists {
		return fmt.Errorf("bucket %q does not exist", b.cfg.Bucket)
	}
	key := ".warpbox-storage-test-" + randomID(6)
	if err := b.Put(ctx, key, bytes.NewReader([]byte("ok")), 2, "text/plain"); err != nil {
		return err
	}
	return b.Delete(ctx, key)
}

func s3BucketLookup(pathStyle bool) minio.BucketLookupType {
	if pathStyle {
		return minio.BucketLookupPath
	}
	return minio.BucketLookupAuto
}

func normalizeS3Endpoint(endpoint string) string {
	endpoint = strings.TrimSpace(endpoint)
	if parsed, err := url.Parse(endpoint); err == nil && parsed.Host != "" {
		return parsed.Host
	}
	return strings.TrimPrefix(strings.TrimPrefix(endpoint, "https://"), "http://")
}
