// Package blob 封装对象存储（MinIO）：大文档正文落对象存储，PG 只留元数据 + 预览 + 对象键。
package blob

import (
	"bytes"
	"context"
	"io"
	"log"
	"time"

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

// Store 是对象存储句柄；cli 为 nil 表示降级（未连上 MinIO，大文档回退内联存 PG）。
type Store struct {
	cli    *minio.Client
	bucket string
}

// Open 连接 MinIO 并确保 bucket 存在。连接失败返回降级实例（cli=nil），不阻断网关启动。
func Open(endpoint, accessKey, secretKey, bucket string) *Store {
	cli, err := minio.New(endpoint, &minio.Options{
		Creds:  credentials.NewStaticV4(accessKey, secretKey, ""),
		Secure: false,
	})
	if err != nil {
		log.Printf("[blob] MinIO 不可用，大文档回退内联: %v", err)
		return &Store{}
	}
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	exists, err := cli.BucketExists(ctx, bucket)
	if err != nil {
		log.Printf("[blob] MinIO 连接失败，大文档回退内联: %v", err)
		return &Store{}
	}
	if !exists {
		if err := cli.MakeBucket(ctx, bucket, minio.MakeBucketOptions{}); err != nil {
			log.Printf("[blob] 建 bucket 失败，大文档回退内联: %v", err)
			return &Store{}
		}
	}
	log.Printf("[blob] MinIO connected %s bucket=%s", endpoint, bucket)
	return &Store{cli: cli, bucket: bucket}
}

// Ready 报告对象存储是否可用。
func (s *Store) Ready() bool { return s != nil && s.cli != nil }

// Put 写入一段文本到对象键 key。
func (s *Store) Put(ctx context.Context, key, content string) error {
	r := bytes.NewReader([]byte(content))
	_, err := s.cli.PutObject(ctx, s.bucket, key, r, int64(len(content)), minio.PutObjectOptions{ContentType: "text/plain; charset=utf-8"})
	return err
}

// Get 读回对象键 key 的全部文本。
func (s *Store) Get(ctx context.Context, key string) (string, error) {
	obj, err := s.cli.GetObject(ctx, s.bucket, key, minio.GetObjectOptions{})
	if err != nil {
		return "", err
	}
	defer obj.Close()
	b, err := io.ReadAll(obj)
	if err != nil {
		return "", err
	}
	return string(b), nil
}

// Delete 删除对象；返回错误供级联删按需失败闭合（其它 best-effort 调用点可忽略返回值）。
func (s *Store) Delete(ctx context.Context, key string) error {
	return s.cli.RemoveObject(ctx, s.bucket, key, minio.RemoveObjectOptions{})
}
