// Copyright 2022 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.

package storage

import (
	"io"
	"net/url"
	"strings"
	"time"

	"github.com/Masterminds/semver/v3"
	"github.com/aliyun/aliyun-oss-go-sdk/oss"
	"github.com/rocboss/paopao-ce/internal/core"
	"github.com/sirupsen/logrus"
)

var (
	_ core.ObjectStorageService = (*aliossServant)(nil)
	_ core.OssCreateService     = (*aliossCreateServant)(nil)
	_ core.OssCreateService     = (*aliossCreateRetentionServant)(nil)
	_ core.OssCreateService     = (*aliossCreateTempDirServant)(nil)
	_ core.VersionInfo          = (*aliossServant)(nil)
)

type aliossCreateServant struct {
	bucket *oss.Bucket
	domain string
}

type aliossCreateRetentionServant struct {
	bucket          *oss.Bucket
	domain          string
	retainInDays    time.Duration
	retainUntilDate time.Time
}

type aliossCreateTempDirServant struct {
	bucket  *oss.Bucket
	domain  string
	tempDir string
}

type aliossServant struct {
	core.OssCreateService

	bucket *oss.Bucket
	domain string
}

func (s *aliossCreateServant) PutObject(objectKey string, reader io.Reader, objectSize int64, contentType string, _persistance bool) (string, error) {
	options := []oss.Option{
		oss.ContentLength(objectSize),
		oss.ContentType(contentType),
	}
	err := s.bucket.PutObject(objectKey, reader, options...)
	if err != nil {
		return "", err
	}
	return s.domain + objectKey, nil
}

func (s *aliossCreateServant) PersistObject(_objectKey string) error {
	// empty
	return nil
}

func (s *aliossCreateRetentionServant) PutObject(objectKey string, reader io.Reader, objectSize int64, contentType string, persistance bool) (string, error) {
	options := []oss.Option{
		oss.ContentLength(objectSize),
		oss.ContentType(contentType),
	}
	if !persistance {
		options = append(options, oss.Expires(time.Now().Add(s.retainInDays)))
	}
	err := s.bucket.PutObject(objectKey, reader, options...)
	if err != nil {
		return "", err
	}
	return s.domain + objectKey, nil
}

func (s *aliossCreateRetentionServant) PersistObject(objectKey string) error {
	return s.bucket.SetObjectMeta(objectKey, oss.Expires(s.retainUntilDate))
}

func (s *aliossCreateTempDirServant) PutObject(objectKey string, reader io.Reader, objectSize int64, contentType string, persistance bool) (string, error) {
	objectName := objectKey
	if !persistance {
		objectName = s.tempDir + objectKey
	}
	options := []oss.Option{
		oss.ContentLength(objectSize),
		oss.ContentType(contentType),
	}
	err := s.bucket.PutObject(objectName, reader, options...)
	if err != nil {
		return "", err
	}
	return s.domain + objectKey, nil
}

func (s *aliossCreateTempDirServant) PersistObject(objectKey string) error {
	exsit, err := s.bucket.IsObjectExist(objectKey)
	if err != nil {
		return err
	}
	if exsit {
		logrus.Debugf("object exist so do nothing objectKey: %s", objectKey)
		return nil
	}
	if _, err := s.bucket.CopyObject(s.tempDir+objectKey, objectKey); err != nil {
		return err
	}
	return s.bucket.DeleteObject(s.tempDir + objectKey)
}

func (s *aliossServant) DeleteObject(objectKey string) error {
	return s.bucket.DeleteObject(objectKey)
}

func (s *aliossServant) DeleteObjects(objectKeys []string) error {
	_, err := s.bucket.DeleteObjects(objectKeys)
	return err
}

func (s *aliossServant) IsObjectExist(objectKey string) (bool, error) {
	return s.bucket.IsObjectExist(objectKey)
}

func (s *aliossServant) SignURL(objectKey string, expiredInSec int64) (string, error) {
	signedURL, err := s.bucket.SignURL(objectKey, oss.HTTPGet, expiredInSec)
	if err != nil {
		logrus.Errorf("client.SignURL err: %v", err)
		return "", err
	}

	ur, err := url.Parse(signedURL)
	if err != nil {
		logrus.Errorf("url.Parse err: %v", err)
		return "", err
	}

	epath, err := url.PathUnescape(ur.Path)
	if err != nil {
		logrus.Errorf("url.PathUnescape err: %v", err)
		return "", err
	}

	ur.Path, ur.RawPath = epath, epath
	return ur.String(), nil
}

func (s *aliossServant) ObjectURL(objetKey string) string {
	return s.domain + objetKey
}

func (s *aliossServant) ObjectKey(objectUrl string) string {
	return strings.Replace(objectUrl, s.domain, "", -1)
}

func (s *aliossServant) Name() string {
	return "AliOSS"
}

func (s *aliossServant) Version() *semver.Version {
	return semver.MustParse("v0.2.0")
}