package s3

import (
	"bytes"
	"context"
	"io"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"gitlink.org.cn/cloudream/common/pkgs/logger"
	clitypes "gitlink.org.cn/cloudream/jcs-pub/client/types"
	"gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/types"
)

type PublicStore struct {
	Detail *clitypes.UserSpaceDetail
	Bucket string
	cli    *s3.Client
}

func NewPublicStore(detail *clitypes.UserSpaceDetail, cli *s3.Client, bkt string) (*PublicStore, error) {
	return &PublicStore{
		Detail: detail,
		Bucket: bkt,
		cli:    cli,
	}, nil
}

func (s *PublicStore) Write(objPath string, stream io.Reader) error {
	key := objPath

	_, err := s.cli.PutObject(context.TODO(), &s3.PutObjectInput{
		Bucket: aws.String(s.Bucket),
		Key:    aws.String(key),
		Body:   stream,
	})

	return err
}

func (s *PublicStore) Read(objPath string) (io.ReadCloser, error) {
	key := objPath

	resp, err := s.cli.GetObject(context.TODO(), &s3.GetObjectInput{
		Bucket: aws.String(s.Bucket),
		Key:    aws.String(key),
	})

	if err != nil {
		return nil, err
	}

	return resp.Body, nil
}

func (s *PublicStore) Mkdir(path string) error {
	_, err := s.cli.PutObject(context.TODO(), &s3.PutObjectInput{
		Bucket: aws.String(s.Bucket),
		Key:    aws.String(path + "/"),
		Body:   bytes.NewReader([]byte{}),
	})
	return err
}

func (s *PublicStore) ListAll(path string) ([]types.PublicStoreEntry, error) {
	key := path
	// TODO 待测试

	input := &s3.ListObjectsInput{
		Bucket:    aws.String(s.Bucket),
		Prefix:    aws.String(key),
		Delimiter: aws.String("/"),
	}

	var objs []types.PublicStoreEntry

	var marker *string
	for {
		input.Marker = marker
		resp, err := s.cli.ListObjects(context.Background(), input)
		if err != nil {
			return nil, err
		}

		for _, obj := range resp.Contents {
			objs = append(objs, types.PublicStoreEntry{
				Path:  *obj.Key,
				Size:  *obj.Size,
				IsDir: false,
			})
		}

		if !*resp.IsTruncated {
			break
		}

		marker = resp.NextMarker
	}

	return objs, nil
}

func (s *PublicStore) getLogger() logger.Logger {
	return logger.WithField("PublicStore", "S3").WithField("Storage", s.Detail.Storage.String())
}

var _ types.BypassPublicRead = (*PublicStore)(nil)

func (s *PublicStore) BypassPublicRead(pa string) (types.BypassFilePath, error) {
	info, err := s.cli.HeadObject(context.TODO(), &s3.HeadObjectInput{
		Bucket: aws.String(s.Bucket),
		Key:    aws.String(pa),
	})
	if err != nil {
		s.getLogger().Warnf("get file %v: %v", pa, err)
		return types.BypassFilePath{}, err
	}

	return types.BypassFilePath{
		Path: pa,
		Info: types.FileInfo{
			Size:        *info.ContentLength,
			Description: pa,
		},
	}, nil
}

var _ types.BypassPublicWrite = (*PublicStore)(nil)

func (s *PublicStore) BypassedPublic(info types.BypassedFileInfo, dstPath string) error {
	_, err := s.cli.CopyObject(context.TODO(), &s3.CopyObjectInput{
		Bucket:     aws.String(s.Bucket),
		CopySource: aws.String(s.Bucket + "/" + info.Path),
		Key:        aws.String(dstPath),
	})
	if err != nil {
		s.getLogger().Warnf("bypass public write %v to %v: %v", info.Path, dstPath, err)
		return err
	}

	return nil
}
