package memorysearch

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"
)

const trashDirName = ".trash"

var trashSweepLocks sync.Map

func trashSweepMutex(derivativeRoot string) *sync.Mutex {
	key := filepath.Clean(derivativeRoot)
	value, _ := trashSweepLocks.LoadOrStore(key, &sync.Mutex{})
	return value.(*sync.Mutex)
}

// moveGenerationToTrash removes a generation from the live namespace with a
// same-filesystem rename. Physical deletion is deliberately separate so
// query/build paths never block on a recursive RemoveAll.
func moveGenerationToTrash(
	derivativeRoot string,
	generationPath string,
	now time.Time,
) (string, error) {
	basename := filepath.Base(generationPath)
	if err := validateBasename(basename); err != nil || basename == "" {
		return "", fmt.Errorf("memory search: trash generation basename %q: %w",
			basename, ErrInvalidBasename)
	}
	expectedParent := filepath.Join(derivativeRoot, generationsDirName)
	if filepath.Clean(filepath.Dir(generationPath)) != filepath.Clean(expectedParent) {
		return "", fmt.Errorf("%w: trash source outside generations: %s",
			ErrGenerationOutsideRoot, generationPath)
	}
	if _, err := os.Lstat(generationPath); err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return "", nil
		}
		return "", err
	}
	trashRoot := filepath.Join(derivativeRoot, trashDirName)
	if err := os.MkdirAll(trashRoot, 0o700); err != nil {
		return "", fmt.Errorf("memory search: mkdir trash: %w", err)
	}
	for attempt := 0; attempt < 16; attempt++ {
		name := strings.Join([]string{
			basename,
			strconv.FormatInt(now.UnixNano(), 10),
			strconv.Itoa(attempt),
		}, ".")
		target := filepath.Join(trashRoot, name)
		if err := os.Rename(generationPath, target); err == nil {
			if err := syncDir(expectedParent); err != nil {
				return "", err
			}
			if err := syncDir(trashRoot); err != nil {
				return "", err
			}
			return target, nil
		} else if !errors.Is(err, os.ErrExist) {
			return "", fmt.Errorf("memory search: rename generation to trash: %w", err)
		}
	}
	return "", fmt.Errorf("memory search: allocate unique trash name for %s", basename)
}

func sweepTrash(derivativeRoot string) error {
	mu := trashSweepMutex(derivativeRoot)
	mu.Lock()
	defer mu.Unlock()
	trashRoot := filepath.Join(derivativeRoot, trashDirName)
	entries, err := os.ReadDir(trashRoot)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return nil
		}
		return err
	}
	var errs []error
	for _, entry := range entries {
		if err := os.RemoveAll(filepath.Join(trashRoot, entry.Name())); err != nil {
			errs = append(errs, fmt.Errorf("remove trash %s: %w", entry.Name(), err))
		}
	}
	return errors.Join(errs...)
}
