package fs

import (
	"context"
	"fmt"
	"os"
	"sync"

	"git.optclblast.xyz/draincloud/draincloud-core/internal/logger"
)

type Storage struct {
	lm  *sync.Map
	dir string
	// If file is not belongs to current node FS - redirect to corresponding node
	// cluster DrainCloudCluster
}

func NewFSStorage(dir string) *Storage {
	return &Storage{
		lm:  &sync.Map{},
		dir: dir,
	}
}

func (s *Storage) GetFile(ctx context.Context, id int64) (*os.File, error) {
	tx := lockFile(s.lm, id)
	defer unlockFile(s.lm, id, tx)

	file, err := os.Open(getFilePath(s.dir, id))
	if err != nil {
		return nil, fmt.Errorf("failed to open file: %w", err)
	}
	defer func() {
		if err = file.Close(); err != nil {
			logger.Error(ctx, "[getFile] close error", logger.Err(err))
		}
	}()

	return file, nil
}

func (s *Storage) SaveBlob(ctx context.Context, id int64, data []byte) error {
	tx := lockFile(s.lm, id)
	defer unlockFile(s.lm, id, tx)

	file, err := os.Open(getFilePath(s.dir, id))
	if err != nil {
		return fmt.Errorf("failed to open file: %w", err)
	}
	defer func() {
		if err = file.Close(); err != nil {
			logger.Error(ctx, "[saveFile] close error", logger.Err(err))
		}
	}()

	if _, err = file.Write(data); err != nil {
		return fmt.Errorf("failed to write data to file: %w", err)
	}

	return nil
}

func (s *Storage) DeleteFile(ctx context.Context, id int64) error {
	tx := lockFile(s.lm, id)
	defer unlockFile(s.lm, id, tx)

	return nil
}

func getFilePath(dir string, id int64) string {
	return fmt.Sprintf("%s/%v", dir, id)
}

func lockFile(lm *sync.Map, id int64) sync.Locker {
	_m := &sync.Mutex{}
	many, _ := lm.LoadOrStore(id, _m)
	_m, _ = many.(*sync.Mutex)
	_m.Lock()
	return _m
}

func unlockFile(lm *sync.Map, id int64, tx sync.Locker) {
	tx.Unlock()
	lm.Delete(id)
}
