package snapshot

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"syscall"

	"github.com/openeuler/Conch/internal/snapshot/common"
)

// prepareSnapshotFiles creates the snapshot directory structure.
func prepareSnapshotFiles(layout *BootLayout) error {
	return os.MkdirAll(layout.SnapDir(), common.DirMode)
}

// listRootfsLayerErofs scans rootfs mount point for layer files in pattern "layer<N>.erofs".
// Returns sorted layer filenames by numeric index.
func listRootfsLayerErofs(rootfsMount string) ([]string, error) {
	entries, err := os.ReadDir(rootfsMount)
	if err != nil {
		return nil, fmt.Errorf("read dir %s: %w", rootfsMount, err)
	}

	const (
		layerPrefix = "layer"
		layerSuffix = ".erofs"
	)

	type layerEntry struct {
		name  string
		index int
	}
	layers := make([]layerEntry, 0, len(entries))

	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}
		name := entry.Name()

		// Check prefix and suffix
		if !strings.HasPrefix(name, layerPrefix) || !strings.HasSuffix(name, layerSuffix) {
			continue
		}

		// Extract numeric part: "layer123.erofs" -> "123"
		numPart := strings.TrimPrefix(name, layerPrefix)
		numPart = strings.TrimSuffix(numPart, layerSuffix)

		// Validate that the extracted part is a valid number
		idx, err := strconv.Atoi(numPart)
		if err != nil {
			// Skip files with non-numeric index (e.g., "layerX.erofs")
			continue
		}
		if idx < 0 {
			continue
		}

		layers = append(layers, layerEntry{name: name, index: idx})
	}

	if len(layers) == 0 {
		return []string{}, nil
	}

	// Sort by numeric index
	sort.Slice(layers, func(i, j int) bool {
		return layers[i].index < layers[j].index
	})

	result := make([]string, len(layers))
	for i, layer := range layers {
		result[i] = layer.name
	}
	return result, nil
}

// prepareSparseMemfile creates a sparse memory file of the specified size.
func prepareSparseMemfile(layout *BootLayout, targetDir string) error {
	memFile := filepath.Join(targetDir, common.MemFileName)
	if err := os.MkdirAll(filepath.Dir(memFile), common.DirMode); err != nil {
		return err
	}

	f, err := os.OpenFile(memFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, common.FileMode)
	if err != nil {
		return fmt.Errorf("open memfile: %w", err)
	}
	defer f.Close()

	if err := f.Truncate(layout.MemorySizeMB * common.MemMB); err != nil {
		return fmt.Errorf("truncate memfile: %w", err)
	}

	return nil
}

// ensureMemFile checks for mem.img existence; creates a sparse file if createIfMissing is true.
func ensureMemFile(layout *BootLayout, memDir string, createIfMissing bool) error {
	memFile := filepath.Join(memDir, common.MemFileName)
	if _, err := os.Stat(memFile); err == nil {
		return nil
	}
	if !createIfMissing {
		return fmt.Errorf("mem.img not found at %s", memFile)
	}
	return prepareSparseMemfile(layout, memDir)
}

func MemKeyFromRootfs(rootfsKey string) string {
	return getMemKeyFromRootfs(rootfsKey)
}

// cleanupEmptySnapshotParents removes empty parent directories after a mount point
// directory has been deleted. It only prunes within the snapshot tree and stops
// at the "snapshot" root directory.
func cleanupEmptySnapshotParents(mountPoint string) error {
	dir := filepath.Dir(mountPoint)
	for {
		base := filepath.Base(dir)
		if base == "." || base == string(filepath.Separator) || base == "snapshot" {
			return nil
		}
		if filepath.Base(filepath.Dir(dir)) == "snapshot" {
			return nil
		}

		err := os.Remove(dir)
		if err == nil {
			dir = filepath.Dir(dir)
			continue
		}
		if os.IsNotExist(err) {
			dir = filepath.Dir(dir)
			continue
		}
		// Non-empty directories stop the prune quietly; other failures bubble up.
		if errors.Is(err, syscall.ENOTEMPTY) || errors.Is(err, syscall.EEXIST) {
			return nil
		}
		return err
	}
}

func bootLayoutLabels(layout *BootLayout, labels map[string]string) map[string]string {
	if labels == nil {
		labels = make(map[string]string)
	}
	labels[common.SnapshotLabel] = "true"
	labels[common.SnapshotLabelMemSize] = fmt.Sprintf("%d", layout.MemorySizeMB)
	labels[common.SnapshotLabelRootfs] = layout.RootfsMount
	labels[common.SnapshotLabelSnapshotDir] = layout.SnapshotDir
	return labels
}