package store

import (
	"bytes"
	"context"
	"errors"
	"github.com/minio/minio-go"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"sync"
)

var once sync.Once

// ErrorInvalidName 无效的文件名
var ErrorInvalidName = errors.New("invalid file name")

// MinioStore minio 对象存储
type MinioStore struct {
	cli *minio.Client
}

// ComposeObject 通过使用服务端拷贝实现钭多个源对象合并创建成一个新的对象。
func (m *MinioStore) ComposeObject(ctx context.Context,pathS []string,filePath string) error {
	bucketName,fileName,err := m.parseFilename(filePath)
	if err != nil{
		return err
	}
	dst, err := minio.NewDestinationInfo(bucketName, fileName, nil, nil)
	if err != nil {
		return  err
	}

	srcs := make([]minio.SourceInfo,0,len(pathS))
	for i:=range pathS{
		bucketName,fileName,err := m.parseFilename(pathS[i])
		if err != nil{
			return err
		}
		srcs = append(srcs,minio.NewSourceInfo(bucketName, fileName, nil))
	}
	return m.cli.ComposeObject(dst, srcs)
}

//RemoveObject 删除minio中的文件
func (m *MinioStore) RemoveObject(ctx context.Context,filePath string) error {
	bucketName,fileName,err := m.parseFilename(filePath)
	if err != nil{
		return err
	}
	return m.cli.RemoveObject(bucketName, fileName)
}


func (m *MinioStore) Delete(ctx context.Context, fileName string) error {
	b, o, err := m.parseFilename(fileName)
	if err != nil {
		return err
	}
	n, err := url.PathUnescape(o)
	if err != nil {
		return err
	}
	return m.cli.RemoveObject(b, n)
}

// Stat 文件状态信息
func (m *MinioStore) Stat(filename string) (minio.ObjectInfo, error) {
	bucketName, objectName, err := m.parseFilename(filename)
	if err != nil {
		return minio.ObjectInfo{}, err
	}
	return m.cli.StatObject(bucketName, objectName, minio.StatObjectOptions{})
}

// Get get file buffers
func (m *MinioStore) Get(ctx context.Context, fileName string) ([]byte, string, error) {
	if ctx == nil {
		ctx = context.Background()
	}

	stat, err := m.Stat(fileName)
	if err != nil {
		return nil, "", errors.New("文件不存在:" + fileName)
	}
	bucketName, objectName, err := m.parseFilename(fileName)
	if err != nil {
		return nil, "", err
	}

	obj, err := m.cli.GetObjectWithContext(ctx, bucketName, objectName, minio.GetObjectOptions{})
	if err != nil {
		return nil, "", err
	}
	buf, err := ioutil.ReadAll(obj)
	if err != nil {
		return nil, "", err
	}
	return buf, stat.ContentType, nil
}

// Store save file
func (m *MinioStore) Store(ctx context.Context, filename string, data io.Reader, size int64) (fileHash string, err error) {
	if ctx == nil {
		ctx = context.Background()
	}
	bucket, objName, err := m.parseFilename(filename)
	if err != nil {
		return
	}
	exists, err := m.cli.BucketExists(bucket)
	if err != nil {
		return
	} else if !exists {
		err = m.cli.MakeBucket(bucket, "local")
		if err != nil {
			return
		}
	}
	buf, err := ioutil.ReadAll(data)
	if err != nil {
		return
	}
	rd := bytes.NewBuffer(buf)
	if size == 0 {
		size = int64(rd.Len())
	}
	_, err = m.cli.PutObjectWithContext(ctx, bucket, objName, rd, size, minio.PutObjectOptions{
		ContentType: http.DetectContentType(buf),
		NumThreads:  2,
	})
	stat, err := m.Stat(filename)
	if err != nil {
		return
	}
	fileHash = stat.ETag
	return
}

// 解析文件名 `prefix/bucket/uuid/filename`
func (m *MinioStore) parseFilename(filename string) (string, string, error) {
	if len(filename) > 0 && filename[0] == '/' {
		filename = filename[1:]
	}
	names := strings.Split(filename, "/")
	if len(names) < 3 {
		return "", "", ErrorInvalidName
	}

	return strings.ToLower(names[1]), strings.Join(names[2:], "/"), nil
}

// MiniStoreInit minio init
func MiniStoreInit(addr, accessKey, secretKey string) *MinioStore {
	ms := new(MinioStore)
	once.Do(func() {
		cli, err := minio.New(addr, accessKey, secretKey, false)
		if err != nil {
			panic(err)
		}
		ms.cli = cli
	})
	return ms
}
