package storage

import (
	"context"
	"fmt"
	"io"
	"kra/internal/biz/system"
	"path"
	"strings"

	"kra/internal/conf"

	"github.com/huaweicloud/huaweicloud-sdk-go-obs/obs"
)

type huaweiStorage struct {
	client                  *obs.ObsClient
	bucket, baseURL, prefix string
}

func newHuaweiStorage(config *conf.AdminBackend_ObjectStore) (system.FileStorage, error) {
	if config == nil || config.Endpoint == "" || config.Bucket == "" || config.AccessKey == "" || config.SecretKey == "" {
		return nil, fmt.Errorf("huawei-obs storage configuration is incomplete")
	}
	client, err := obs.New(config.AccessKey, config.SecretKey, config.Endpoint)
	if err != nil {
		return nil, err
	}
	return &huaweiStorage{client: client, bucket: config.Bucket, baseURL: strings.TrimSuffix(config.BaseUrl, "/"), prefix: strings.Trim(config.PathPrefix, "/")}, nil
}
func (s *huaweiStorage) key(name string) string {
	if s.prefix == "" {
		return strings.TrimPrefix(name, "/")
	}
	return path.Join(s.prefix, strings.TrimPrefix(name, "/"))
}
func (s *huaweiStorage) unkey(key string) string {
	return strings.TrimPrefix(strings.TrimPrefix(key, s.prefix), "/")
}
func (s *huaweiStorage) file(key string, size int64) *system.StoredFile {
	name := s.unkey(key)
	fileURL := s.baseURL + "/" + key
	return &system.StoredFile{Name: path.Base(name), Path: name, URL: fileURL, Size: size}
}
func (s *huaweiStorage) Put(_ context.Context, name string, reader io.Reader) (*system.StoredFile, error) {
	key := s.key(name)
	if _, err := s.client.PutObject(&obs.PutObjectInput{PutObjectBasicInput: obs.PutObjectBasicInput{ObjectOperationInput: obs.ObjectOperationInput{Bucket: s.bucket, Key: key}}, Body: reader}); err != nil {
		return nil, err
	}
	meta, err := s.client.GetObjectMetadata(&obs.GetObjectMetadataInput{Bucket: s.bucket, Key: key})
	if err != nil {
		return nil, err
	}
	return s.file(key, meta.ContentLength), nil
}
func (s *huaweiStorage) Open(_ context.Context, name string) (io.ReadCloser, error) {
	result, err := s.client.GetObject(&obs.GetObjectInput{GetObjectMetadataInput: obs.GetObjectMetadataInput{Bucket: s.bucket, Key: s.key(name)}})
	if err != nil {
		return nil, err
	}
	return result.Body, nil
}
func (s *huaweiStorage) Delete(_ context.Context, name string) error {
	_, err := s.client.DeleteObject(&obs.DeleteObjectInput{Bucket: s.bucket, Key: s.key(name)})
	return err
}
func (s *huaweiStorage) Compose(ctx context.Context, names []string, destination string) (*system.StoredFile, string, error) {
	return composeFiles(ctx, s, names, destination)
}
func (s *huaweiStorage) DeletePrefix(ctx context.Context, prefix string) error {
	prefix, err := normalizeDeletePrefix(prefix)
	if err != nil {
		return err
	}
	cursor := ""
	for {
		items, next, more, err := s.List(ctx, prefix+"/", cursor, 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
		}
		cursor, err = advanceDeletePrefixCursor(cursor, next, more)
		if err != nil {
			return err
		}
	}
}
func (s *huaweiStorage) List(_ context.Context, prefix, cursor string, limit int) ([]*system.StoredFile, string, bool, error) {
	if limit <= 0 {
		limit = 100
	}
	result, err := s.client.ListObjects(&obs.ListObjectsInput{ListObjsInput: obs.ListObjsInput{Prefix: boundedPrefix(s.key(prefix), prefix), MaxKeys: limit}, Bucket: s.bucket, Marker: cursor})
	if err != nil {
		return nil, "", false, err
	}
	items := make([]*system.StoredFile, 0, len(result.Contents))
	for _, object := range result.Contents {
		item := s.file(object.Key, object.Size)
		item.LastModified = object.LastModified
		items = append(items, item)
	}
	next := result.NextMarker
	if result.IsTruncated && next == "" && len(result.Contents) > 0 {
		next = result.Contents[len(result.Contents)-1].Key
	}
	return items, next, result.IsTruncated, nil
}
