package rm

import (
	"context"
	"fmt"
	"os"
	"strings"

	"github.com/go-errors/errors"
	"github.com/spf13/afero"
	"github.com/supabase/cli/internal/storage/client"
	"github.com/supabase/cli/internal/storage/cp"
	"github.com/supabase/cli/internal/storage/ls"
	"github.com/supabase/cli/internal/utils"
	"github.com/supabase/cli/internal/utils/flags"
	"github.com/supabase/cli/pkg/storage"
)

var (
	errMissingObject = errors.New("Object not found")
	errMissingBucket = errors.New("You must specify a bucket to delete.")
	errMissingFlag   = errors.New("You must specify -r flag to delete directories.")
)

type PrefixGroup struct {
	Bucket   string
	Prefixes []string
}

func Run(ctx context.Context, paths []string, recursive bool, fsys afero.Fs) error {
	// Group paths by buckets
	groups := map[string][]string{}
	for _, objectPath := range paths {
		remotePath, err := client.ParseStorageURL(objectPath)
		if err != nil {
			return err
		}
		bucket, prefix := client.SplitBucketPrefix(remotePath)
		// Ignore attempts to delete all buckets
		if len(bucket) == 0 {
			return errors.New(errMissingBucket)
		}
		if cp.IsDir(prefix) && !recursive {
			return errors.New(errMissingFlag)
		}
		groups[bucket] = append(groups[bucket], prefix)
	}
	api, err := client.NewStorageAPI(ctx, flags.ProjectRef)
	if err != nil {
		return err
	}
	for bucket, prefixes := range groups {
		confirm := fmt.Sprintf("Confirm deleting files in bucket %v?", utils.Bold(bucket))
		if shouldDelete, err := utils.NewConsole().PromptYesNo(ctx, confirm, false); err != nil {
			return err
		} else if !shouldDelete {
			continue
		}
		// Always try deleting first in case the paths resolve to extensionless files
		fmt.Fprintln(os.Stderr, "Deleting objects:", prefixes)
		removed, err := api.DeleteObjects(ctx, bucket, prefixes)
		if err != nil {
			return err
		}
		set := map[string]struct{}{}
		for _, object := range removed {
			set[object.Name] = struct{}{}
		}
		for _, prefix := range prefixes {
			if _, ok := set[prefix]; ok {
				continue
			}
			if !recursive {
				fmt.Fprintln(os.Stderr, "Object not found:", prefix)
				continue
			}
			if len(prefix) > 0 {
				prefix += "/"
			}
			if err := RemoveStoragePathAll(ctx, api, bucket, prefix); err != nil {
				return err
			}
		}
	}
	return nil
}

// Expects prefix to be terminated by "/" or ""
func RemoveStoragePathAll(ctx context.Context, api storage.StorageAPI, bucket, prefix string) error {
	// We must remove one directory at a time to avoid breaking pagination result
	queue := make([]string, 0)
	queue = append(queue, prefix)
	for len(queue) > 0 {
		dirPrefix := queue[len(queue)-1]
		queue = queue[:len(queue)-1]
		paths, err := ls.ListStoragePaths(ctx, api, fmt.Sprintf("/%s/%s", bucket, dirPrefix))
		if err != nil {
			return err
		}
		if len(paths) == 0 && len(prefix) > 0 {
			return errors.Errorf("%w: %s/%s", errMissingObject, bucket, prefix)
		}
		var files []string
		for _, objectName := range paths {
			objectPrefix := dirPrefix + objectName
			if strings.HasSuffix(objectName, "/") {
				queue = append(queue, objectPrefix)
			} else {
				files = append(files, objectPrefix)
			}
		}
		if len(files) > 0 {
			fmt.Fprintln(os.Stderr, "Deleting objects:", files)
			if _, err := api.DeleteObjects(ctx, bucket, files); err != nil {
				return err
			}
		}
	}
	if len(prefix) == 0 {
		fmt.Fprintln(os.Stderr, "Deleting bucket:", bucket)
		if data, err := api.DeleteBucket(ctx, bucket); err == nil {
			fmt.Fprintln(os.Stderr, data.Message)
		} else if strings.Contains(err.Error(), `"error":"Bucket not found"`) {
			fmt.Fprintln(os.Stderr, "Bucket not found:", bucket)
		} else {
			return err
		}
	}
	return nil
}
