package storage

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"strings"
	"time"

	"github.com/qiniu/go-sdk/v7/auth/qbox"
	qstorage "github.com/qiniu/go-sdk/v7/storage"
	"kra/internal/biz"
	"kra/internal/conf"
)

type qiniuStorage struct {
	config  *conf.AdminBackend_Qiniu
	upload  *qstorage.FormUploader
	manager *qstorage.BucketManager
	mac     *qbox.Mac
	token   string
}

func newQiniuStorage(config *conf.AdminBackend_Qiniu) (biz.FileStorage, error) {
	if config == nil || config.Bucket == "" || config.AccessKey == "" || config.SecretKey == "" {
		return nil, fmt.Errorf("qiniu storage configuration is incomplete")
	}
	cfg := qstorage.Config{UseHTTPS: config.UseHttps, UseCdnDomains: config.UseCdnDomains}
	switch config.Zone {
	case "ZoneHuadong":
		cfg.Zone = &qstorage.ZoneHuadong
	case "ZoneHuabei":
		cfg.Zone = &qstorage.ZoneHuabei
	case "ZoneHuanan":
		cfg.Zone = &qstorage.ZoneHuanan
	case "ZoneBeimei":
		cfg.Zone = &qstorage.ZoneBeimei
	case "ZoneXinjiapo":
		cfg.Zone = &qstorage.ZoneXinjiapo
	}
	mac := qbox.NewMac(config.AccessKey, config.SecretKey)
	policy := qstorage.PutPolicy{Scope: config.Bucket}
	token := policy.UploadToken(mac)
	return &qiniuStorage{config: config, upload: qstorage.NewFormUploader(&cfg), manager: qstorage.NewBucketManager(mac, &cfg), mac: mac, token: token}, nil
}
func (s *qiniuStorage) file(key string, size int64) *biz.StoredFile {
	return &biz.StoredFile{Name: path.Base(key), Path: key, URL: strings.TrimSuffix(s.config.BaseUrl, "/") + "/" + key, Size: size}
}
func (s *qiniuStorage) Put(ctx context.Context, name string, reader io.Reader) (*biz.StoredFile, error) {
	temporary, err := os.CreateTemp("", "kra-qiniu-upload-*")
	if err != nil {
		return nil, err
	}
	temporaryName := temporary.Name()
	defer os.Remove(temporaryName)
	size, err := io.Copy(temporary, reader)
	if err != nil {
		_ = temporary.Close()
		return nil, err
	}
	if _, err = temporary.Seek(0, io.SeekStart); err != nil {
		_ = temporary.Close()
		return nil, err
	}
	defer temporary.Close()
	ret := qstorage.PutRet{}
	if err = s.upload.Put(ctx, &ret, s.token, strings.TrimPrefix(name, "/"), temporary, size, &qstorage.PutExtra{}); err != nil {
		return nil, err
	}
	return s.file(ret.Key, size), nil
}
func (s *qiniuStorage) Open(ctx context.Context, name string) (io.ReadCloser, error) {
	downloadURL := qstorage.MakePrivateURL(s.mac, strings.TrimSuffix(s.config.BaseUrl, "/"), strings.TrimPrefix(name, "/"), time.Now().Add(time.Hour).Unix())
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, nil)
	if err != nil {
		return nil, err
	}
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode >= 300 {
		_ = resp.Body.Close()
		return nil, fmt.Errorf("qiniu download failed: %s", resp.Status)
	}
	return resp.Body, nil
}
func (s *qiniuStorage) Delete(ctx context.Context, name string) error {
	return s.manager.Delete(s.config.Bucket, strings.TrimPrefix(name, "/"))
}
func (s *qiniuStorage) Compose(ctx context.Context, names []string, destination string) (*biz.StoredFile, string, error) {
	return composeFiles(ctx, s, names, destination)
}
func (s *qiniuStorage) DeletePrefix(ctx context.Context, prefix string) error {
	for {
		items, _, more, err := s.List(ctx, prefix, "", 1000)
		if err != nil {
			return err
		}
		for _, item := range items {
			if err = s.Delete(ctx, item.Path); err != nil {
				return err
			}
		}
		if !more {
			return nil
		}
	}
}
func (s *qiniuStorage) List(ctx context.Context, prefix, cursor string, limit int) ([]*biz.StoredFile, string, bool, error) {
	if limit <= 0 {
		limit = 100
	}
	entries, _, marker, more, err := s.manager.ListFiles(s.config.Bucket, prefix, "", cursor, limit)
	if err != nil {
		return nil, "", false, err
	}
	out := make([]*biz.StoredFile, 0, len(entries))
	for _, entry := range entries {
		item := s.file(entry.Key, entry.Fsize)
		item.LastModified = time.Unix(0, entry.PutTime*100)
		item.ContentType = entry.MimeType
		out = append(out, item)
	}
	return out, marker, more, nil
}
