package miniohelper

import (
	"context"
	"errors"
	"fmt"
	"net/url"
	"path"

	m "simrs-vx/internal/infra/minio"

	"github.com/minio/minio-go/v7"
)

type minioRepository struct {
	client *minio.Client
}

var I minioRepository = minioRepository{}

func (repo *minioRepository) SetClient() {
	repo.client = m.I
}

// Check exist bucket if not exist create Bucket
func (repo *minioRepository) createBucket(bucketName string, region string) error {
	exist, err := repo.client.BucketExists(context.Background(), bucketName)
	if err != nil {
		return err
	}
	if !exist {
		// create bucket
		if err := repo.client.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: region}); err != nil {
			return err
		}

		// set bucket policy to public read
		policy := fmt.Sprintf(`{
			"Version": "2012-10-17",
			"Statement": [
				{
					"Effect": "Allow",
					"Principal": {"AWS": ["*"]},
					"Action": ["s3:GetObject"],
					"Resource": ["arn:aws:s3:::%s/*"]
				}
			]
		}`, bucketName)

		if err := repo.client.SetBucketPolicy(context.Background(), bucketName, policy); err != nil {
			return err
		}
	}

	return nil
}

// Upload file reader to MinIO
func (repo *minioRepository) PutObject(input UploadReaderInput) (*minio.UploadInfo, error) {
	if err := repo.createBucket(input.BucketName, m.O.GetRegion()); err != nil {
		return nil, err
	}
	options := minio.PutObjectOptions{
		ContentType:  input.ContentType,
		UserMetadata: map[string]string{"x-amz-acl": "public-read"},
	}

	uploadInfo, err := repo.client.PutObject(context.Background(), input.BucketName, input.Name, input.File, input.Size, options)
	if err != nil {
		return nil, err
	}

	return &uploadInfo, err
}

// Upload file path to MinIO
func (repo *minioRepository) FPutObject(input UploadPathInput) (*minio.UploadInfo, error) {
	if err := repo.createBucket(input.BucketName, m.O.GetRegion()); err != nil {
		return nil, err
	}
	options := minio.PutObjectOptions{
		ContentType:  input.ContentType,
		UserMetadata: map[string]string{"x-amz-acl": "public-read"},
	}

	uploadInfo, err := repo.client.FPutObject(context.Background(), input.BucketName, input.Name, input.Path, options)
	if err != nil {
		return nil, err
	}

	return &uploadInfo, err
}

// Move file from old to new MinIO path
func (repo *minioRepository) MoveObject(src minio.CopySrcOptions, dst minio.CopyDestOptions) (*minio.UploadInfo, error) {
	uploadInfo, err := repo.client.CopyObject(context.Background(), dst, src)
	if err != nil {
		return nil, err
	}
	if err := repo.RemoveObject(src.Bucket, src.Object); err != nil {
		return nil, err
	}
	return &uploadInfo, err
}

// Generatet public link MinIO
func (repo *minioRepository) GenerateUrl(bucket, object string) string {
	mode := "http"
	if m.O.GetUseSsl() {
		mode = "https"
	}
	return fmt.Sprintf("%s://%s/%s/%s", mode, m.O.GetEndpoint(), bucket, object)
}

// Download file from MinIO
func (repo *minioRepository) GetObject(bucket string, fileName string) (*minio.Object, error) {
	object, err := repo.client.GetObject(context.Background(), bucket, fileName, minio.GetObjectOptions{})
	if err != nil {
		return nil, err
	}
	return object, nil
}

// Delete file from MinIO
func (repo *minioRepository) RemoveObject(bucket string, fileName string) error {
	if err := repo.client.RemoveObject(context.Background(), bucket, fileName, minio.RemoveObjectOptions{}); err != nil {
		return err
	}
	return nil
}

// create presigned url to post object with custom policy
func (repo *minioRepository) GeneratePresignedPost(policy *minio.PostPolicy) (*url.URL, map[string]string, error) {
	presignedUrl, formData, err := repo.client.PresignedPostPolicy(context.Background(), policy)
	if err != nil {
		return nil, nil, err
	}
	return presignedUrl, formData, nil
}

// create presigned url to get object
func (repo *minioRepository) GeneratePresignedGetObject(input PresignedGetInput) (*url.URL, error) {
	presignedUrl, err := repo.client.PresignedGetObject(context.Background(), input.Bucket, input.Object, input.Expiry, input.ReqParams)
	if err != nil {
		return nil, err
	}
	return presignedUrl, nil
}

func getBucketName(idx int) string {
	return m.O.GetBucketName()[idx]
}

func GetBucketPatient() (string, error) {
	bucketName := getBucketName(0)
	if bucketName == "" {
		return "", errors.New("bucket name unknown")
	}
	return bucketName, nil
}

func GetEndpointUrl(bucket string) string {
	mode := "http"
	if m.O.GetUseSsl() {
		mode = "https"
	}
	return fmt.Sprintf("%s://%s/%s", mode, m.O.GetEndpoint(), bucket)
}

func GetFilename(rawUrl string) (string, error) {
	parsed, err := url.Parse(rawUrl)
	if err != nil {
		return "", err
	}
	return path.Base(parsed.Path), nil
}
