package storage

import (
	"context"
	"io"
	"net/url"

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

// Config holds the S3-compatible (MinIO) connection settings.
type Config struct {
	Endpoint        string
	AccessKeyID     string
	SecretAccessKey string
	Bucket          string
	Region          string
	UseSSL          bool
	MaxFileBytes    int64
}

// Client is the thin object-store adapter. The domain layer imports this
// package instead of minio-go directly, keeping the S3 client at the platform
// boundary like cache and database.
type Client struct {
	mc       *minio.Client
	bucket   string
	region   string
	maxBytes int64
}

func NewClient(cfg Config) (*Client, error) {
	// minio-go 的 Endpoint 参数必须是裸 host[:port],不接受带 scheme 的完整
	// URL(否则报 "Endpoint url cannot have fully qualified paths.")。这里剥离
	// scheme,并由 scheme 推导 Secure(https→true),二者都比 S3_USE_SSL 优先。
	secure := cfg.UseSSL
	host := cfg.Endpoint
	if parsed, err := url.Parse(cfg.Endpoint); err == nil && parsed.Scheme != "" {
		secure = parsed.Scheme == "https"
		host = parsed.Host
	}
	mc, err := minio.New(host, &minio.Options{
		Creds:  credentials.NewStaticV4(cfg.AccessKeyID, cfg.SecretAccessKey, ""),
		Secure: secure,
		Region: cfg.Region,
	})
	if err != nil {
		return nil, err
	}
	mc.SetAppInfo("ai-gateway", "0.10.0")
	return &Client{mc: mc, bucket: cfg.Bucket, region: cfg.Region, maxBytes: cfg.MaxFileBytes}, nil
}

// EnsureBucket creates the configured bucket if it does not exist. Safe to call
// repeatedly; it is a no-op once the bucket exists.
func (c *Client) EnsureBucket(ctx context.Context) error {
	exists, err := c.mc.BucketExists(ctx, c.bucket)
	if err != nil {
		return err
	}
	if exists {
		return nil
	}
	return c.mc.MakeBucket(ctx, c.bucket, minio.MakeBucketOptions{Region: c.region})
}

func (c *Client) MaxBytes() int64 { return c.maxBytes }

// PutObject streams a reader to the object store. A size of -1 lets the client
// use chunked multipart upload so files are not buffered in memory.
func (c *Client) PutObject(ctx context.Context, key string, r io.Reader, size int64, contentType string) (int64, error) {
	info, err := c.mc.PutObject(ctx, c.bucket, key, r, size, minio.PutObjectOptions{ContentType: contentType})
	if err != nil {
		return 0, err
	}
	return info.Size, nil
}

// OpenObject returns a streaming reader plus the object size.
func (c *Client) OpenObject(ctx context.Context, key string) (io.ReadCloser, int64, error) {
	obj, err := c.mc.GetObject(ctx, c.bucket, key, minio.GetObjectOptions{})
	if err != nil {
		return nil, 0, err
	}
	stat, err := obj.Stat()
	if err != nil {
		_ = obj.Close()
		return nil, 0, err
	}
	return obj, stat.Size, nil
}

func (c *Client) DeleteObject(ctx context.Context, key string) error {
	return c.mc.RemoveObject(ctx, c.bucket, key, minio.RemoveObjectOptions{})
}
