package metabase

import (
	"errors"
	"fmt"
	"sync"
	"time"

	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
	engineconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine"
	shardconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine/shard"
	meta "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/metabase"
	"github.com/spf13/cobra"
	"golang.org/x/sync/errgroup"
)

const (
	pathFlag      = "path"
	noCompactFlag = "no-compact"
)

var errNoPathsFound = errors.New("no metabase paths found")

var path string

var UpgradeCmd = &cobra.Command{
	Use:   "upgrade",
	Short: "Upgrade metabase to latest version",
	RunE:  upgrade,
}

func upgrade(cmd *cobra.Command, _ []string) error {
	configFile, err := cmd.Flags().GetString(commonflags.ConfigFlag)
	if err != nil {
		return err
	}
	configDir, err := cmd.Flags().GetString(commonflags.ConfigDirFlag)
	if err != nil {
		return err
	}
	noCompact, _ := cmd.Flags().GetBool(noCompactFlag)
	var paths []string
	if path != "" {
		paths = append(paths, path)
	}
	appCfg := config.New(configFile, configDir, config.EnvPrefix)
	if err := engineconfig.IterateShards(appCfg, false, func(sc *shardconfig.Config) error {
		paths = append(paths, sc.Metabase().Path())
		return nil
	}); err != nil {
		return fmt.Errorf("failed to get metabase paths: %w", err)
	}
	if len(paths) == 0 {
		return errNoPathsFound
	}
	cmd.Println("found", len(paths), "metabases:")
	for i, path := range paths {
		cmd.Println(i+1, ":", path)
	}
	result := make(map[string]bool)
	var resultGuard sync.Mutex
	eg, ctx := errgroup.WithContext(cmd.Context())
	for _, path := range paths {
		eg.Go(func() error {
			var success bool
			cmd.Println("upgrading metabase", path, "...")
			if err := meta.Upgrade(ctx, path, !noCompact, func(a ...any) {
				cmd.Println(append([]any{time.Now().Format(time.RFC3339), ":", path, ":"}, a...)...)
			}); err != nil {
				cmd.Println("error: failed to upgrade metabase", path, ":", err)
			} else {
				success = true
				cmd.Println("metabase", path, "upgraded successfully")
			}
			resultGuard.Lock()
			result[path] = success
			resultGuard.Unlock()
			return nil
		})
	}
	if err := eg.Wait(); err != nil {
		return err
	}
	for mb, ok := range result {
		if ok {
			cmd.Println(mb, ": success")
		} else {
			cmd.Println(mb, ": failed")
		}
	}
	return nil
}

func initUpgradeCommand() {
	flags := UpgradeCmd.Flags()
	flags.StringVar(&path, pathFlag, "", "Path to metabase file")
	flags.Bool(noCompactFlag, false, "Do not compact upgraded metabase file")
}