package client

import (
	"context"
	"crypto/tls"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"

	"github.com/1Panel-dev/1Panel/agent/buserr"
	"github.com/minio/minio-go/v7"
	"github.com/minio/minio-go/v7/pkg/credentials"
)

type minIoClient struct {
	bucket string
	client *minio.Client
}

func NewMinIoClient(vars map[string]interface{}) (*minIoClient, error) {
	endpoint := loadParamFromVars("endpoint", vars)
	accessKeyID := loadParamFromVars("accessKey", vars)
	secretAccessKey := loadParamFromVars("secretKey", vars)
	bucket := loadParamFromVars("bucket", vars)
	ssl := strings.Split(endpoint, ":")[0]
	if len(ssl) == 0 || (ssl != "https" && ssl != "http") {
		return nil, buserr.WithDetail("ErrInvalidParams", "ssl", fmt.Errorf("no such proto in ssl: %s", ssl))
	}

	secure := false
	tlsConfig := &tls.Config{}
	if ssl == "https" {
		secure = true
		tlsConfig.InsecureSkipVerify = true
	}
	var transport http.RoundTripper = &http.Transport{
		TLSClientConfig: tlsConfig,
	}
	client, err := minio.New(strings.ReplaceAll(endpoint, ssl+"://", ""), &minio.Options{
		Creds:     credentials.NewStaticV4(accessKeyID, secretAccessKey, ""),
		Secure:    secure,
		Transport: transport,
	})
	if err != nil {
		return nil, err
	}
	return &minIoClient{bucket: bucket, client: client}, nil
}

func (m minIoClient) ListBuckets() ([]interface{}, error) {
	buckets, err := m.client.ListBuckets(context.Background())
	if err != nil {
		return nil, err
	}
	var result []interface{}
	for _, bucket := range buckets {
		result = append(result, bucket.Name)
	}
	return result, err
}

func (m minIoClient) Exist(path string) (bool, error) {
	if _, err := m.client.GetObject(context.Background(), m.bucket, path, minio.GetObjectOptions{}); err != nil {
		return false, err
	}
	return true, nil
}

func (m minIoClient) Size(path string) (int64, error) {
	obj, err := m.client.GetObject(context.Background(), m.bucket, path, minio.GetObjectOptions{})
	if err != nil {
		return 0, err
	}
	file, err := obj.Stat()
	if err != nil {
		return 0, err
	}
	return file.Size, nil
}

func (m minIoClient) Delete(path string) (bool, error) {
	object, err := m.client.GetObject(context.Background(), m.bucket, path, minio.GetObjectOptions{})
	if err != nil {
		return false, err
	}
	info, err := object.Stat()
	if err != nil {
		return false, err
	}
	if err = m.client.RemoveObject(context.Background(), m.bucket, path, minio.RemoveObjectOptions{
		GovernanceBypass: true,
		VersionID:        info.VersionID,
	}); err != nil {
		return false, err
	}
	return true, nil
}

func (m minIoClient) Upload(ctx context.Context, src, target string) (bool, error) {
	file, err := os.Open(src)
	if err != nil {
		return false, err
	}
	defer file.Close()

	fileStat, err := file.Stat()
	if err != nil {
		return false, err
	}
	_, err = m.client.PutObject(ctx, m.bucket, target, file, fileStat.Size(), minio.PutObjectOptions{ContentType: "application/octet-stream"})
	if err != nil {
		return false, err
	}
	return true, nil
}

func (m minIoClient) Download(src, target string) (bool, error) {
	object, err := m.client.GetObject(context.Background(), m.bucket, src, minio.GetObjectOptions{})
	if err != nil {
		return false, err
	}
	defer object.Close()
	localFile, err := os.Create(target)
	if err != nil {
		return false, err
	}
	defer localFile.Close()
	if _, err = io.Copy(localFile, object); err != nil {
		return false, err
	}
	return true, nil
}

func (m minIoClient) ListObjects(prefix string) ([]string, error) {
	opts := minio.ListObjectsOptions{
		Recursive: true,
		Prefix:    prefix,
	}

	var result []string
	for object := range m.client.ListObjects(context.Background(), m.bucket, opts) {
		if object.Err != nil {
			continue
		}
		result = append(result, object.Key)
	}
	return result, nil
}
