package storage

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

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

var (
	_ core.ObjectStorageService = (*huaweiobsServant)(nil)
	_ core.OssCreateService     = (*hwobsCreateServant)(nil)
	_ core.OssCreateService     = (*hwobsCreateRetentionServant)(nil)
	_ core.OssCreateService     = (*hwobsCreateTempDirServant)(nil)
	_ core.VersionInfo          = (*huaweiobsServant)(nil)
)

type hwobsCreateServant struct {
	client *obs.ObsClient
	bucket string
	domain string
}

type hwobsCreateRetentionServant struct {
	client          *obs.ObsClient
	bucket          string
	domain          string
	retainInDays    int64
	retainUntilDays string
}

type hwobsCreateTempDirServant struct {
	client  *obs.ObsClient
	bucket  string
	domain  string
	tempDir string
}

type huaweiobsServant struct {
	core.OssCreateService

	client *obs.ObsClient
	bucket string
	domain string
}

func (s *hwobsCreateServant) PutObject(objectKey string, reader io.Reader, objectSize int64, contentType string, _persistance bool) (string, error) {
	input := &obs.PutObjectInput{}
	input.Bucket, input.Key, input.Body = s.bucket, objectKey, reader
	input.ContentType, input.ContentLength = contentType, objectSize
	_, err := s.client.PutObject(input)
	if err != nil {
		return "", err
	}
	return s.domain + objectKey, nil
}

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

func (s *hwobsCreateRetentionServant) PutObject(objectKey string, reader io.Reader, objectSize int64, contentType string, persistance bool) (string, error) {
	input := &obs.PutObjectInput{}
	input.Bucket, input.Key, input.Body = s.bucket, objectKey, reader
	input.ContentType, input.ContentLength = contentType, objectSize
	if !persistance {
		input.Expires = s.retainInDays
	}
	_, err := s.client.PutObject(input)
	if err != nil {
		return "", err
	}
	return s.domain + objectKey, nil
}

func (s *hwobsCreateRetentionServant) PersistObject(objectKey string) error {
	_, err := s.client.SetObjectMetadata(&obs.SetObjectMetadataInput{
		Expires: s.retainUntilDays,
	})
	return err
}

func (s *hwobsCreateTempDirServant) PutObject(objectKey string, reader io.Reader, objectSize int64, contentType string, persistance bool) (string, error) {
	objectName := objectKey
	if !persistance {
		objectName = s.tempDir + objectKey
	}
	input := &obs.PutObjectInput{}
	input.Bucket, input.Key, input.Body = s.bucket, objectName, reader
	input.ContentType, input.ContentLength = contentType, objectSize
	_, err := s.client.PutObject(input)
	if err != nil {
		return "", err
	}
	return s.domain + objectKey, nil
}

func (s *hwobsCreateTempDirServant) PersistObject(objectKey string) error {
	// is object exist that key equel objectKey
	input := &obs.GetObjectMetadataInput{
		Bucket: s.bucket,
		Key:    objectKey,
	}
	_, err := s.client.GetObjectMetadata(input)
	if err == nil {
		logrus.Debugf("object exist so do nothing objectKey: %s", objectKey)
		return nil
	}

	tmpObjKey := s.tempDir + objectKey
	copyInput := &obs.CopyObjectInput{
		CopySourceBucket: s.bucket,
		CopySourceKey:    tmpObjKey,
	}
	copyInput.Bucket, copyInput.Key = s.bucket, objectKey
	if _, err = s.client.CopyObject(copyInput); err != nil {
		return err
	}

	_, err = s.client.DeleteObject(&obs.DeleteObjectInput{
		Bucket: s.bucket,
		Key:    tmpObjKey,
	})
	return err
}

func (s *huaweiobsServant) DeleteObject(objectKey string) error {
	_, err := s.client.DeleteObject(&obs.DeleteObjectInput{
		Bucket: s.bucket,
		Key:    objectKey,
	})
	return err
}

func (s *huaweiobsServant) DeleteObjects(objectKeys []string) error {
	input := &obs.DeleteObjectsInput{
		Bucket:  s.bucket,
		Objects: make([]obs.ObjectToDelete, 0, len(objectKeys)),
	}

	for _, key := range objectKeys {
		input.Objects = append(input.Objects, obs.ObjectToDelete{Key: key})
	}

	_, err := s.client.DeleteObjects(input)
	return err
}

func (s *huaweiobsServant) IsObjectExist(objectKey string) (bool, error) {
	input := &obs.GetObjectMetadataInput{
		Bucket: s.bucket,
		Key:    objectKey,
	}
	if _, err := s.client.GetObjectMetadata(input); err != nil {
		return false, err
	}
	return true, nil
}

func (s *huaweiobsServant) SignURL(objectKey string, expiredInSec int64) (string, error) {
	input := &obs.CreateSignedUrlInput{
		Method:  obs.HttpMethodGet,
		Bucket:  s.bucket,
		Key:     objectKey,
		Expires: int(expiredInSec),
	}
	out, err := s.client.CreateSignedUrl(input)
	if err != nil {
		logrus.Errorf("huaweiobsServant.SignURL err: %v", err)
		return "", err
	}

	ur, err := url.Parse(out.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 *huaweiobsServant) ObjectURL(objetKey string) string {
	return s.domain + objetKey
}

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

func (s *huaweiobsServant) Name() string {
	return "HuaweiOBS"
}

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