package minio

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

var (
	internalClient *Client
	once           sync.Once
)

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

// Init 初始化minio客户端
func Init(addr, accessKey, secretKey string) *Client {
	once.Do(func() {
		cli, err := minio.New(addr, accessKey, secretKey, false)
		if err != nil {
			panic(err)
		}
		internalClient = &Client{cli}
	})
	return internalClient
}

// GetClient 获取文件存储客户端
func GetClient() *Client {
	return internalClient
}

// Client minio客户端
type Client struct {
	cli *minio.Client
}

// MinioClient 文件存储客户端
func (a *Client) MinioClient() *minio.Client {
	return a.cli
}

// Store 保存文件
// filename 前3段约束(第一段约束为(前缀)，第二段为bucket(业务类型)，第三段以后为文件key)
func (a *Client) Store(ctx context.Context, filename string, data io.Reader, size int64) error {
	if ctx == nil {
		ctx = context.Background()
	}

	bucket, objName, err := a.parseFilename(filename)
	if err != nil {
		return err
	}

	exists, err := a.cli.BucketExists(bucket)
	if err != nil {
		return err
	} else if !exists {
		err = a.cli.MakeBucket(bucket, "local")
		if err != nil {
			return err
		}
	}

	buf, err := ioutil.ReadAll(data)
	if err != nil {
		return err
	}

	rd := bytes.NewBuffer(buf)
	if size == 0 {
		size = int64(rd.Len())
	}

	_, err = a.cli.PutObjectWithContext(ctx, bucket, objName, rd, size, minio.PutObjectOptions{
		ContentType: http.DetectContentType(buf),
		NumThreads:  2,
	})

	return err
}

// 解析文件名
func (a *Client) 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
}

// Get 获取文件对象
func (a *Client) Get(ctx context.Context, filename string) (*minio.Object, error) {
	if ctx == nil {
		ctx = context.Background()
	}

	bucketName, objectName, err := a.parseFilename(filename)
	if err != nil {
		return nil, err
	}

	return a.cli.GetObjectWithContext(ctx, bucketName, objectName, minio.GetObjectOptions{})
}

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

	return a.cli.StatObject(bucketName, objectName, minio.StatObjectOptions{})
}

// GetBase64 获取文件的base64数据
func (a *Client) GetBase64(ctx context.Context, filename string) (string, error) {
	obj, err := a.Get(ctx, filename)
	if err != nil {
		return "", err
	}
	defer obj.Close()

	buf, err := ioutil.ReadAll(obj)
	if err != nil {
		return "", err
	}

	s := base64.StdEncoding.EncodeToString(buf)
	return s, nil
}
