package blobovniczatree

import (
	"context"
	"strings"

	"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard/mode"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util"
	"go.uber.org/zap"
	"golang.org/x/sync/errgroup"
)

// Open opens blobovnicza tree.
func (b *Blobovniczas) Open(mode mode.ComponentMode) error {
	b.readOnly = mode.ReadOnly()
	b.metrics.SetMode(mode)
	b.metrics.SetRebuildStatus(rebuildStatusNotStarted)
	b.openManagers()
	return nil
}

// Init initializes blobovnicza tree.
//
// Should be called exactly once.
func (b *Blobovniczas) Init() error {
	b.log.Debug(context.Background(), logs.BlobovniczatreeInitializingBlobovniczas)

	if b.readOnly {
		b.log.Debug(context.Background(), logs.BlobovniczatreeReadonlyModeSkipBlobovniczasInitialization)
		return nil
	}

	return b.initializeDBs(context.TODO())
}

func (b *Blobovniczas) initializeDBs(ctx context.Context) error {
	err := util.MkdirAllX(b.rootPath, b.perm)
	if err != nil {
		return err
	}

	eg, egCtx := errgroup.WithContext(ctx)
	eg.SetLimit(b.blzInitWorkerCount)
	err = b.iterateIncompletedRebuildDBPaths(egCtx, func(p string) (bool, error) {
		eg.Go(func() error {
			p = strings.TrimSuffix(p, rebuildSuffix)
			shBlz := b.getBlobovniczaWithoutCaching(p)
			blz, err := shBlz.Open(egCtx)
			if err != nil {
				return err
			}
			defer shBlz.Close(egCtx)

			moveInfo, err := blz.ListMoveInfo(egCtx)
			if err != nil {
				return err
			}
			for _, move := range moveInfo {
				b.deleteProtectedObjects.Add(move.Address)
			}

			b.log.Debug(egCtx, logs.BlobovniczatreeBlobovniczaSuccessfullyInitializedClosing, zap.String("id", p))
			return nil
		})
		return false, nil
	})
	if err != nil {
		_ = eg.Wait()
		return err
	}

	return eg.Wait()
}

func (b *Blobovniczas) openManagers() {
	b.commondbManager.Open() // order important
	b.activeDBManager.Open()
	b.dbCache.Open()
}

// Close implements common.Storage.
func (b *Blobovniczas) Close(ctx context.Context) error {
	b.dbCache.Close() // order important
	b.activeDBManager.Close(ctx)
	b.commondbManager.Close()

	return nil
}

// returns blobovnicza with path p
//
// If blobovnicza is already cached, instance from cache is returned w/o changes.
func (b *Blobovniczas) getBlobovnicza(ctx context.Context, p string) *sharedDB {
	return b.dbCache.GetOrCreate(ctx, p)
}

func (b *Blobovniczas) getBlobovniczaWithoutCaching(p string) *sharedDB {
	return b.commondbManager.GetByPath(p)
}