package blobtree

import (
	"context"
	"encoding/binary"
	"os"
	"strconv"

	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common"
)

const (
	tempFileSymbols = "###"
	storageIDLength = 8
)

func (b *BlobTree) Put(_ context.Context, prm common.PutPrm) (common.PutRes, error) {
	if b.cfg.readOnly {
		return common.PutRes{}, common.ErrReadOnly
	}

	dir := b.getDirectoryPath(prm.Address)

	if err := b.createDir(dir); err != nil {
		return common.PutRes{}, err
	}

	if !prm.DontCompress {
		prm.RawData = b.compressor.Compress(prm.RawData)
	}

	idx, err := b.saveToFile(prm, dir)
	if err != nil {
		return common.PutRes{}, err
	}

	storageID := make([]byte, storageIDLength)
	binary.LittleEndian.PutUint64(storageID, idx)
	return common.PutRes{StorageID: storageID}, nil
}

func (b *BlobTree) saveToFile(prm common.PutPrm, dir string) (uint64, error) {
	returnIdx := true
	idx := b.dispatcher.GetIdxForWrite(dir)
	path := b.getFilePath(dir, idx)

	b.fileLock.Lock(path)
	defer b.fileLock.Unlock(path)

	defer func() {
		if returnIdx {
			b.dispatcher.ReturnIdx(dir, idx)
		}
	}()

	currentContent, err := b.readFileContent(path)
	if err != nil {
		return 0, err
	}
	var newRecord objectData
	newRecord.Address = prm.Address
	newRecord.Data = prm.RawData

	size, err := b.writeToTmpAndRename(append(currentContent, newRecord), path)
	if err != nil {
		return 0, err
	}
	returnIdx = size < b.cfg.targetFileSizeBytes

	return idx, nil
}

func (b *BlobTree) writeToTmpAndRename(records []objectData, path string) (uint64, error) {
	tmpFile := path + tempFileSymbols + strconv.FormatUint(b.suffix.Add(1), 16)

	size, err := b.saveContentToFile(records, tmpFile)
	if err != nil {
		_ = os.Remove(tmpFile)
		return 0, err
	}

	if err := os.Rename(tmpFile, path); err != nil {
		_ = os.Remove(tmpFile)
		return 0, err
	}

	return size, nil
}