package frostfs

import (
	"context"
	"fmt"

	"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/cache"
	"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/handler"
	"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs"
	cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
	"go.uber.org/zap"
)

type Source struct {
	frostFS     *FrostFS
	netmapCache *cache.NetmapCache
	bucketCache *cache.BucketCache
	log         *zap.Logger
}

func NewSource(frostFS *FrostFS, netmapCache *cache.NetmapCache, bucketCache *cache.BucketCache, log *zap.Logger) *Source {
	return &Source{
		frostFS:     frostFS,
		netmapCache: netmapCache,
		bucketCache: bucketCache,
		log:         log,
	}
}

func (s *Source) NetMapSnapshot(ctx context.Context) (netmap.NetMap, error) {
	cachedNetmap := s.netmapCache.Get()
	if cachedNetmap != nil {
		return *cachedNetmap, nil
	}

	netmapSnapshot, err := s.frostFS.NetmapSnapshot(ctx)
	if err != nil {
		return netmap.NetMap{}, fmt.Errorf("get netmap: %w", err)
	}

	if err = s.netmapCache.Put(netmapSnapshot); err != nil {
		s.log.Warn(logs.CouldntCacheNetmap, zap.Error(err), logs.TagField(logs.TagDatapath))
	}

	return netmapSnapshot, nil
}

func (s *Source) PlacementPolicy(ctx context.Context, cnrID cid.ID) (netmap.PlacementPolicy, error) {
	info := s.bucketCache.GetByCID(cnrID)
	if info != nil {
		return info.PlacementPolicy, nil
	}

	prm := handler.PrmContainer{
		ContainerID: cnrID,
	}
	res, err := s.frostFS.Container(ctx, prm)
	if err != nil {
		return netmap.PlacementPolicy{}, fmt.Errorf("get container: %w", err)
	}

	// We don't put container back to the cache to keep cache
	// coherent to the requests made by users. FrostFS Source
	// is being used by SDK Tree Pool and it should not fill cache
	// with possibly irrelevant container values.

	return res.PlacementPolicy(), nil
}