package files

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"io"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"sync"
	"time"
	"unicode/utf8"

	"zlh-agent/internal/mods"
	"zlh-agent/internal/state"
)

const (
	MaxListEntries     = 200
	MaxReadSize        = 2 * 1024 * 1024
	MaxWriteSize       = 1 * 1024 * 1024
	binaryProbeLen     = 8192
	shadowDirName      = ".zlh-shadow"
	metadataFileName   = ".zlh_metadata.json"
	shadowCleanupEvery = 6 * time.Hour
	shadowRetention    = 7 * 24 * time.Hour
	shadowOriginalName = "original"
	shadowMetadataName = "metadata.json"
	MaxModUploadSize   = 250 * 1024 * 1024
	MaxDataPackSize    = 100 * 1024 * 1024
	MaxDevUploadSize   = 250 * 1024 * 1024
	DevWorkspaceRoot   = "/home/dev/workspace"
)

var (
	ErrInvalidPath   = errors.New("invalid path")
	ErrPathEscape    = errors.New("path outside runtime root")
	ErrForbiddenPath = errors.New("path not allowed")
	ErrNotFile       = errors.New("path is not a file")
	ErrNotDir        = errors.New("path is not a directory")
	ErrTooLarge      = errors.New("file exceeds 2MB limit")
	ErrWriteTooLarge = errors.New("file exceeds 1MB limit")
	ErrBinaryFile    = errors.New("binary file")
	ErrDeleteDenied  = errors.New("delete not allowed for this path")
	ErrWriteDenied   = errors.New("write not allowed for this path")
	ErrUploadDenied  = errors.New("upload denied")
	ErrAlreadyExists = errors.New("file already exists")

	shadowCleanupOnce sync.Once
)

type Entry struct {
	Name     string `json:"name"`
	Path     string `json:"path"`
	Type     string `json:"type"`
	Size     int64  `json:"size"`
	Modified string `json:"modified"`
}

type StatResponse struct {
	Name       string  `json:"name"`
	Path       string  `json:"path"`
	Type       string  `json:"type"`
	Size       int64   `json:"size"`
	Modified   string  `json:"modified"`
	IsWritable bool    `json:"isWritable"`
	HasBackup  bool    `json:"hasBackup"`
	Source     *string `json:"source"`
}

type ListResponse struct {
	Path      string  `json:"path"`
	Entries   []Entry `json:"entries"`
	Limit     int     `json:"limit"`
	Truncated bool    `json:"truncated"`
}

type ReadResponse struct {
	Path      string `json:"path"`
	Size      int64  `json:"size"`
	Content   string `json:"content"`
	Truncated bool   `json:"truncated"`
}

type shadowMetadata struct {
	Path           string `json:"path"`
	CreatedAt      string `json:"created_at"`
	LastModifiedAt string `json:"last_modified_at"`
}

type Meta struct {
	Source     string    `json:"source"`
	UploadedAt time.Time `json:"uploaded_at"`
}

func RuntimeRoot(cfg *state.Config) string {
	if cfg != nil && strings.EqualFold(cfg.ContainerType, "dev") {
		return DevWorkspaceRoot
	}
	return mods.ResolveServerRoot(cfg)
}

func NormalizeVisiblePath(rel string) (string, error) {
	return normalizeAndCheckVisibleRel(rel)
}

func StartShadowCleanup() {
	shadowCleanupOnce.Do(func() {
		go func() {
			ticker := time.NewTicker(shadowCleanupEvery)
			defer ticker.Stop()
			cleanupShadowRootFromConfig()
			for range ticker.C {
				cleanupShadowRootFromConfig()
			}
		}()
	})
}

func ResolveWorldPath(root, rel string) (string, string, error) {
	resolvedRoot, err := filepath.EvalSymlinks(filepath.Clean(root))
	if err != nil {
		return "", "", err
	}

	normalizedRel, err := normalizeRelativePath(rel)
	if err != nil {
		return "", "", err
	}
	if isHiddenInternalPath(normalizedRel) {
		return "", "", ErrForbiddenPath
	}

	joined := filepath.Join(resolvedRoot, filepath.FromSlash(normalizedRel))
	resolvedPath, err := filepath.EvalSymlinks(joined)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return "", normalizedRel, os.ErrNotExist
		}
		return "", "", err
	}
	resolvedPath = filepath.Clean(resolvedPath)
	if !withinRoot(resolvedRoot, resolvedPath) {
		return "", "", ErrPathEscape
	}

	finalRel, err := filepath.Rel(resolvedRoot, resolvedPath)
	if err != nil {
		return "", "", err
	}
	if finalRel == "." {
		finalRel = ""
	}
	finalRel = filepath.ToSlash(finalRel)
	if isHiddenInternalPath(finalRel) {
		return "", "", ErrForbiddenPath
	}
	return resolvedPath, finalRel, nil
}

func List(root, rel string) (ListResponse, error) {
	resolvedPath, responsePath, err := ResolveWorldPath(root, rel)
	if err != nil {
		return ListResponse{}, err
	}

	info, err := os.Stat(resolvedPath)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return ListResponse{}, os.ErrNotExist
		}
		return ListResponse{}, err
	}
	if !info.IsDir() {
		return ListResponse{}, ErrNotDir
	}

	entries, err := os.ReadDir(resolvedPath)
	if err != nil {
		return ListResponse{}, err
	}
	sort.Slice(entries, func(i, j int) bool {
		leftIsDir := dirEntryIsDir(entries[i])
		rightIsDir := dirEntryIsDir(entries[j])
		if leftIsDir != rightIsDir {
			return leftIsDir
		}
		return strings.ToLower(entries[i].Name()) < strings.ToLower(entries[j].Name())
	})

	respEntries := make([]Entry, 0, min(len(entries), MaxListEntries))
	truncated := len(entries) > MaxListEntries
	for _, entry := range entries {
		if len(respEntries) >= MaxListEntries {
			break
		}
		if isHiddenInternalName(entry.Name()) {
			continue
		}
		childRel := entry.Name()
		if responsePath != "" {
			childRel = filepath.ToSlash(filepath.Join(responsePath, entry.Name()))
		}
		childPath, childRespPath, err := ResolveWorldPath(root, childRel)
		if err != nil {
			continue
		}
		childInfo, err := os.Stat(childPath)
		if err != nil {
			continue
		}
		entryType := "file"
		if childInfo.IsDir() {
			entryType = "dir"
		}
		respEntries = append(respEntries, Entry{
			Name:     entry.Name(),
			Path:     childRespPath,
			Type:     entryType,
			Size:     childInfo.Size(),
			Modified: childInfo.ModTime().UTC().Format(time.RFC3339),
		})
	}

	return ListResponse{
		Path:      responsePath,
		Entries:   respEntries,
		Limit:     MaxListEntries,
		Truncated: truncated,
	}, nil
}

func Stat(containerType, root, rel string) (StatResponse, error) {
	resolvedPath, responsePath, err := ResolveWorldPath(root, rel)
	if err != nil {
		return StatResponse{}, err
	}
	info, err := os.Stat(resolvedPath)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return StatResponse{}, os.ErrNotExist
		}
		return StatResponse{}, err
	}
	entryType := "file"
	if info.IsDir() {
		entryType = "dir"
	}
	name := filepath.Base(resolvedPath)
	if responsePath == "" {
		name = filepath.Base(filepath.Clean(root))
	}
	return StatResponse{
		Name:       name,
		Path:       responsePath,
		Type:       entryType,
		Size:       info.Size(),
		Modified:   info.ModTime().UTC().Format(time.RFC3339),
		IsWritable: isWritablePath(containerType, root, responsePath),
		HasBackup:  hasBackup(root, responsePath),
		Source:     sourceForPath(root, responsePath),
	}, nil
}

func Read(root, rel string) (ReadResponse, error) {
	resolvedPath, responsePath, err := ResolveWorldPath(root, rel)
	if err != nil {
		return ReadResponse{}, err
	}
	info, err := os.Stat(resolvedPath)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return ReadResponse{}, os.ErrNotExist
		}
		return ReadResponse{}, err
	}
	if info.IsDir() {
		return ReadResponse{}, ErrNotFile
	}
	if info.Size() > MaxReadSize {
		return ReadResponse{}, ErrTooLarge
	}

	data, err := os.ReadFile(resolvedPath)
	if err != nil {
		return ReadResponse{}, err
	}
	if isBinary(data) {
		return ReadResponse{}, ErrBinaryFile
	}
	return ReadResponse{
		Path:      responsePath,
		Size:      int64(len(data)),
		Content:   string(data),
		Truncated: false,
	}, nil
}

func OpenDownload(root, rel string) (*os.File, os.FileInfo, string, error) {
	resolvedPath, _, err := ResolveWorldPath(root, rel)
	if err != nil {
		return nil, nil, "", err
	}
	info, err := os.Stat(resolvedPath)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return nil, nil, "", os.ErrNotExist
		}
		return nil, nil, "", err
	}
	if info.IsDir() {
		return nil, nil, "", ErrNotFile
	}
	file, err := os.Open(resolvedPath)
	if err != nil {
		return nil, nil, "", err
	}
	return file, info, filepath.Base(resolvedPath), nil
}

func Delete(containerType, root, rel string) (string, error) {
	resolvedRoot, err := filepath.EvalSymlinks(filepath.Clean(root))
	if err != nil {
		return "", err
	}

	normalizedRel, err := normalizeAndCheckVisibleRel(rel)
	if err != nil {
		return "", err
	}
	if !isAllowedDelete(containerType, normalizedRel) {
		return "", ErrDeleteDenied
	}

	resolvedPath, err := resolveExistingNoFollowFinal(resolvedRoot, normalizedRel)
	if err != nil {
		return "", err
	}
	info, err := os.Lstat(resolvedPath)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return "", os.ErrNotExist
		}
		return "", err
	}
	if info.IsDir() {
		return "", ErrDeleteDenied
	}
	if info.Mode()&os.ModeSymlink != 0 {
		targetPath, err := filepath.EvalSymlinks(resolvedPath)
		if err != nil && !errors.Is(err, os.ErrNotExist) {
			return "", err
		}
		if err == nil && !withinRoot(resolvedRoot, filepath.Clean(targetPath)) {
			return "", ErrPathEscape
		}
	}

	if err := os.Remove(resolvedPath); err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return "", os.ErrNotExist
		}
		return "", err
	}
	return normalizedRel, nil
}

func Write(containerType, root, rel string, data []byte) (bool, error) {
	resolvedRoot, err := filepath.EvalSymlinks(filepath.Clean(root))
	if err != nil {
		return false, err
	}

	normalizedRel, err := normalizeAndCheckVisibleRel(rel)
	if err != nil {
		return false, err
	}
	if !isAllowedWrite(containerType, normalizedRel) {
		return false, ErrWriteDenied
	}
	if len(data) > MaxWriteSize {
		return false, ErrWriteTooLarge
	}
	if isBinary(data) {
		return false, ErrBinaryFile
	}

	resolvedPath, err := resolveExistingNoFollowFinal(resolvedRoot, normalizedRel)
	if err != nil {
		return false, err
	}
	info, err := os.Lstat(resolvedPath)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return false, os.ErrNotExist
		}
		return false, err
	}
	if info.IsDir() {
		return false, ErrWriteDenied
	}
	if info.Mode()&os.ModeSymlink != 0 {
		return false, ErrWriteDenied
	}

	backupCreated, err := ensureShadow(resolvedRoot, normalizedRel, resolvedPath)
	if err != nil {
		return false, err
	}
	if err := writeAtomic(resolvedPath, data, info.Mode().Perm()); err != nil {
		return false, err
	}
	if err := updateShadowMetadata(resolvedRoot, normalizedRel, backupCreated); err != nil {
		return false, err
	}
	return backupCreated, nil
}

func Revert(containerType, root, rel string) (string, error) {
	resolvedRoot, err := filepath.EvalSymlinks(filepath.Clean(root))
	if err != nil {
		return "", err
	}

	normalizedRel, err := normalizeAndCheckVisibleRel(rel)
	if err != nil {
		return "", err
	}
	if !isAllowedWrite(containerType, normalizedRel) {
		return "", ErrWriteDenied
	}

	resolvedPath, err := resolveForRestore(resolvedRoot, normalizedRel)
	if err != nil {
		return "", err
	}
	if info, err := os.Lstat(resolvedPath); err == nil {
		if info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
			return "", ErrWriteDenied
		}
	} else if !errors.Is(err, os.ErrNotExist) {
		return "", err
	}

	shadowDir := shadowPath(resolvedRoot, normalizedRel)
	originalPath := filepath.Join(shadowDir, shadowOriginalName)
	originalInfo, err := os.Stat(originalPath)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return "", os.ErrNotExist
		}
		return "", err
	}
	if originalInfo.IsDir() {
		return "", os.ErrNotExist
	}
	data, err := os.ReadFile(originalPath)
	if err != nil {
		return "", err
	}
	if err := writeAtomic(resolvedPath, data, originalInfo.Mode().Perm()); err != nil {
		return "", err
	}
	if err := os.RemoveAll(shadowDir); err != nil {
		return "", err
	}
	return normalizedRel, nil
}

func Upload(containerType, root, rel string, r io.Reader, sizeLimit int64, overwrite bool) (int64, bool, error) {
	resolvedRoot, err := filepath.EvalSymlinks(filepath.Clean(root))
	if err != nil {
		return 0, false, err
	}

	normalizedRel, err := normalizeAndCheckVisibleRel(rel)
	if err != nil {
		return 0, false, err
	}
	allowedLimit, ok := uploadLimitForPath(containerType, normalizedRel)
	if !ok {
		return 0, false, ErrUploadDenied
	}
	if sizeLimit <= 0 || sizeLimit > allowedLimit {
		sizeLimit = allowedLimit
	}

	resolvedPath, err := resolveUploadTarget(resolvedRoot, normalizedRel)
	if err != nil {
		return 0, false, err
	}

	overwritten := false
	if info, err := os.Lstat(resolvedPath); err == nil {
		if info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
			return 0, false, ErrUploadDenied
		}
		if !overwrite {
			return 0, false, ErrAlreadyExists
		}
		overwritten = true
	} else if !errors.Is(err, os.ErrNotExist) {
		return 0, false, err
	}

	dir := filepath.Dir(resolvedPath)
	tmp, err := os.CreateTemp(dir, ".zlh-upload-*")
	if err != nil {
		return 0, false, err
	}
	tmpPath := tmp.Name()
	defer func() {
		_ = os.Remove(tmpPath)
	}()

	limited := io.LimitReader(r, sizeLimit+1)
	written, err := io.Copy(tmp, limited)
	if closeErr := tmp.Close(); err == nil && closeErr != nil {
		err = closeErr
	}
	if err != nil {
		return 0, false, err
	}
	if written > sizeLimit {
		return 0, false, ErrWriteTooLarge
	}
	if err := os.Chmod(tmpPath, 0o644); err != nil {
		return 0, false, err
	}
	if err := os.Rename(tmpPath, resolvedPath); err != nil {
		return 0, false, err
	}
	if err := updateMetadata(resolvedRoot, normalizedRel); err != nil {
		return 0, false, err
	}
	return written, overwritten, nil
}

func shadowPath(root, rel string) string {
	sum := sha256.Sum256([]byte(rel))
	return filepath.Join(root, shadowDirName, hex.EncodeToString(sum[:]))
}

func normalizeRelativePath(rel string) (string, error) {
	rel = strings.TrimSpace(rel)
	if rel == "" || rel == "." {
		return "", nil
	}
	if filepath.IsAbs(rel) || strings.HasPrefix(rel, "/") {
		return "", ErrInvalidPath
	}
	for _, r := range rel {
		if r == 0 || r < 32 || r == 127 {
			return "", ErrInvalidPath
		}
	}
	cleaned := filepath.Clean(rel)
	if cleaned == "." {
		return "", nil
	}
	if cleaned == ".." || strings.HasPrefix(cleaned, ".."+string(filepath.Separator)) {
		return "", ErrInvalidPath
	}
	return filepath.ToSlash(cleaned), nil
}

func withinRoot(root, target string) bool {
	rel, err := filepath.Rel(root, target)
	if err != nil {
		return false
	}
	return rel == "." || (rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator)))
}

func dirEntryIsDir(entry os.DirEntry) bool {
	if entry.IsDir() {
		return true
	}
	if entry.Type()&os.ModeSymlink != 0 {
		if info, err := entry.Info(); err == nil {
			return info.IsDir()
		}
	}
	return false
}

func isBinary(data []byte) bool {
	if len(data) == 0 {
		return false
	}
	probe := data
	if len(probe) > binaryProbeLen {
		probe = probe[:binaryProbeLen]
	}
	if bytes.IndexByte(probe, 0) >= 0 {
		return true
	}
	if !utf8.Valid(probe) {
		return true
	}
	return false
}

func isAllowedDelete(containerType, rel string) bool {
	if strings.EqualFold(containerType, "dev") {
		return rel != ""
	}
	parts := strings.Split(rel, "/")
	if len(parts) != 2 {
		return false
	}
	switch parts[0] {
	case "mods-removed", "mods-uploaded":
		return parts[1] != ""
	case "logs":
		return strings.HasSuffix(parts[1], ".log") || strings.HasSuffix(parts[1], ".log.gz")
	default:
		return false
	}
}

func isAllowedWrite(containerType, rel string) bool {
	if strings.EqualFold(containerType, "dev") {
		return rel != ""
	}
	parts := strings.Split(rel, "/")
	if len(parts) == 1 {
		return parts[0] == "server.properties"
	}
	if len(parts) != 2 || parts[0] != "config" {
		return false
	}
	ext := strings.ToLower(filepath.Ext(parts[1]))
	switch ext {
	case ".toml", ".json", ".properties":
		return true
	default:
		return false
	}
}

func uploadLimitForPath(containerType, rel string) (int64, bool) {
	if strings.EqualFold(containerType, "dev") {
		return MaxDevUploadSize, rel != ""
	}
	parts := strings.Split(rel, "/")
	switch {
	case len(parts) == 2 && parts[0] == "mods" && strings.HasSuffix(strings.ToLower(parts[1]), ".jar"):
		return MaxModUploadSize, true
	case len(parts) == 3 && parts[0] == "world" && parts[1] == "datapacks" && strings.HasSuffix(strings.ToLower(parts[2]), ".zip"):
		return MaxDataPackSize, true
	default:
		return 0, false
	}
}

func isWritablePath(containerType, root, rel string) bool {
	if !isAllowedWrite(containerType, rel) {
		return false
	}

	resolvedRoot, err := filepath.EvalSymlinks(filepath.Clean(root))
	if err != nil {
		return false
	}
	resolvedPath, err := resolveExistingNoFollowFinal(resolvedRoot, rel)
	if err != nil {
		return false
	}
	info, err := os.Lstat(resolvedPath)
	if err != nil {
		return false
	}
	if info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
		return false
	}
	return true
}

func hasBackup(root, rel string) bool {
	if rel == "" {
		return false
	}
	resolvedRoot, err := filepath.EvalSymlinks(filepath.Clean(root))
	if err != nil {
		return false
	}
	originalPath := filepath.Join(shadowPath(resolvedRoot, rel), shadowOriginalName)
	info, err := os.Stat(originalPath)
	if err != nil {
		return false
	}
	return !info.IsDir()
}

func sourceForPath(root, rel string) *string {
	if rel == "" {
		return nil
	}
	resolvedRoot, err := filepath.EvalSymlinks(filepath.Clean(root))
	if err != nil {
		return nil
	}
	meta, err := loadMetadata(resolvedRoot)
	if err != nil {
		return nil
	}
	entry, ok := meta[rel]
	if !ok || strings.TrimSpace(entry.Source) == "" {
		return nil
	}
	source := entry.Source
	return &source
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func normalizeAndCheckVisibleRel(rel string) (string, error) {
	normalizedRel, err := normalizeRelativePath(rel)
	if err != nil {
		return "", err
	}
	if isHiddenInternalPath(normalizedRel) {
		return "", ErrForbiddenPath
	}
	return normalizedRel, nil
}

func isShadowPath(rel string) bool {
	if rel == "" {
		return false
	}
	return rel == shadowDirName || strings.HasPrefix(rel, shadowDirName+"/")
}

func isHiddenInternalPath(rel string) bool {
	if rel == "" {
		return false
	}
	return isShadowPath(rel) || rel == metadataFileName
}

func isHiddenInternalName(name string) bool {
	return name == shadowDirName || name == metadataFileName
}

func resolveExistingNoFollowFinal(resolvedRoot, rel string) (string, error) {
	candidate := filepath.Join(resolvedRoot, filepath.FromSlash(rel))
	parentPath := filepath.Dir(candidate)
	resolvedParent, err := filepath.EvalSymlinks(parentPath)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return "", os.ErrNotExist
		}
		return "", err
	}
	resolvedParent = filepath.Clean(resolvedParent)
	if !withinRoot(resolvedRoot, resolvedParent) {
		return "", ErrPathEscape
	}
	resolvedPath := filepath.Clean(filepath.Join(resolvedParent, filepath.Base(candidate)))
	if !withinRoot(resolvedRoot, resolvedPath) {
		return "", ErrPathEscape
	}
	return resolvedPath, nil
}

func resolveForRestore(resolvedRoot, rel string) (string, error) {
	candidate := filepath.Join(resolvedRoot, filepath.FromSlash(rel))
	parentPath := filepath.Dir(candidate)
	resolvedParent, err := filepath.EvalSymlinks(parentPath)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return "", os.ErrNotExist
		}
		return "", err
	}
	resolvedParent = filepath.Clean(resolvedParent)
	if !withinRoot(resolvedRoot, resolvedParent) {
		return "", ErrPathEscape
	}
	resolvedPath := filepath.Clean(filepath.Join(resolvedParent, filepath.Base(candidate)))
	if !withinRoot(resolvedRoot, resolvedPath) {
		return "", ErrPathEscape
	}
	return resolvedPath, nil
}

func resolveUploadTarget(resolvedRoot, rel string) (string, error) {
	candidate := filepath.Join(resolvedRoot, filepath.FromSlash(rel))
	parentPath := filepath.Dir(candidate)
	resolvedParent, err := filepath.EvalSymlinks(parentPath)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return "", os.ErrNotExist
		}
		return "", err
	}
	resolvedParent = filepath.Clean(resolvedParent)
	if !withinRoot(resolvedRoot, resolvedParent) {
		return "", ErrPathEscape
	}
	parentInfo, err := os.Stat(resolvedParent)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return "", os.ErrNotExist
		}
		return "", err
	}
	if !parentInfo.IsDir() {
		return "", ErrUploadDenied
	}
	resolvedPath := filepath.Clean(filepath.Join(resolvedParent, filepath.Base(candidate)))
	if !withinRoot(resolvedRoot, resolvedPath) {
		return "", ErrPathEscape
	}
	return resolvedPath, nil
}

func ensureShadow(resolvedRoot, rel, resolvedPath string) (bool, error) {
	shadowDir := shadowPath(resolvedRoot, rel)
	originalPath := filepath.Join(shadowDir, shadowOriginalName)
	if _, err := os.Stat(originalPath); err == nil {
		return false, nil
	} else if !errors.Is(err, os.ErrNotExist) {
		return false, err
	}

	if err := os.MkdirAll(shadowDir, 0o755); err != nil {
		return false, err
	}
	data, err := os.ReadFile(resolvedPath)
	if err != nil {
		return false, err
	}
	info, err := os.Stat(resolvedPath)
	if err != nil {
		return false, err
	}
	if err := os.WriteFile(originalPath, data, info.Mode().Perm()); err != nil {
		return false, err
	}
	now := time.Now().UTC()
	meta := shadowMetadata{
		Path:           rel,
		CreatedAt:      now.Format(time.RFC3339),
		LastModifiedAt: now.Format(time.RFC3339),
	}
	if err := writeShadowMetadataFile(filepath.Join(shadowDir, shadowMetadataName), meta); err != nil {
		return false, err
	}
	return true, nil
}

func updateShadowMetadata(resolvedRoot, rel string, backupCreated bool) error {
	shadowDir := shadowPath(resolvedRoot, rel)
	metaPath := filepath.Join(shadowDir, shadowMetadataName)
	now := time.Now().UTC()
	meta := shadowMetadata{
		Path:           rel,
		CreatedAt:      now.Format(time.RFC3339),
		LastModifiedAt: now.Format(time.RFC3339),
	}
	if !backupCreated {
		if existing, err := readShadowMetadata(metaPath); err == nil {
			meta = existing
			if strings.TrimSpace(meta.Path) == "" {
				meta.Path = rel
			}
			if strings.TrimSpace(meta.CreatedAt) == "" {
				meta.CreatedAt = now.Format(time.RFC3339)
			}
			meta.LastModifiedAt = now.Format(time.RFC3339)
		}
	}
	return writeShadowMetadataFile(metaPath, meta)
}

func writeShadowMetadataFile(path string, meta shadowMetadata) error {
	data, err := json.Marshal(meta)
	if err != nil {
		return err
	}
	data = append(data, '\n')
	return os.WriteFile(path, data, 0o644)
}

func readShadowMetadata(path string) (shadowMetadata, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return shadowMetadata{}, err
	}
	var meta shadowMetadata
	if err := json.Unmarshal(data, &meta); err != nil {
		return shadowMetadata{}, err
	}
	return meta, nil
}

func writeAtomic(dest string, data []byte, mode os.FileMode) error {
	dir := filepath.Dir(dest)
	tmp, err := os.CreateTemp(dir, ".zlh-write-*")
	if err != nil {
		return err
	}
	tmpPath := tmp.Name()
	defer func() {
		_ = os.Remove(tmpPath)
	}()
	if _, err := tmp.Write(data); err != nil {
		tmp.Close()
		return err
	}
	if err := tmp.Sync(); err != nil {
		tmp.Close()
		return err
	}
	if err := tmp.Close(); err != nil {
		return err
	}
	if err := os.Chmod(tmpPath, mode); err != nil {
		return err
	}
	return os.Rename(tmpPath, dest)
}

func cleanupShadowRootFromConfig() {
	cfg, err := state.LoadConfig()
	if err != nil {
		return
	}
	cleanupShadowRoot(RuntimeRoot(cfg))
}

func cleanupShadowRoot(root string) {
	resolvedRoot, err := filepath.EvalSymlinks(filepath.Clean(root))
	if err != nil {
		return
	}
	base := filepath.Join(resolvedRoot, shadowDirName)
	entries, err := os.ReadDir(base)
	if err != nil {
		return
	}
	now := time.Now().UTC()
	for _, entry := range entries {
		if !entry.IsDir() {
			continue
		}
		shadowDir := filepath.Join(base, entry.Name())
		meta, err := readShadowMetadata(filepath.Join(shadowDir, shadowMetadataName))
		if err != nil {
			continue
		}
		ts, err := time.Parse(time.RFC3339, strings.TrimSpace(meta.LastModifiedAt))
		if err != nil {
			continue
		}
		if now.Sub(ts) > shadowRetention {
			_ = os.RemoveAll(shadowDir)
		}
	}
}

func loadMetadata(root string) (map[string]Meta, error) {
	path := filepath.Join(root, metadataFileName)
	data, err := os.ReadFile(path)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return map[string]Meta{}, nil
		}
		return nil, err
	}
	var meta map[string]Meta
	if err := json.Unmarshal(data, &meta); err != nil {
		return nil, err
	}
	if meta == nil {
		meta = map[string]Meta{}
	}
	return meta, nil
}

func writeMetadata(root string, data map[string]Meta) error {
	payload, err := json.Marshal(data)
	if err != nil {
		return err
	}
	payload = append(payload, '\n')
	target := filepath.Join(root, metadataFileName)
	tmp, err := os.CreateTemp(root, ".zlh-metadata-*")
	if err != nil {
		return err
	}
	tmpPath := tmp.Name()
	defer func() {
		_ = os.Remove(tmpPath)
	}()
	if _, err := tmp.Write(payload); err != nil {
		tmp.Close()
		return err
	}
	if err := tmp.Sync(); err != nil {
		tmp.Close()
		return err
	}
	if err := tmp.Close(); err != nil {
		return err
	}
	if err := os.Chmod(tmpPath, 0o644); err != nil {
		return err
	}
	return os.Rename(tmpPath, target)
}

func updateMetadata(root, rel string) error {
	meta, err := loadMetadata(root)
	if err != nil {
		return err
	}
	meta[rel] = Meta{
		Source:     "user",
		UploadedAt: time.Now().UTC(),
	}
	return writeMetadata(root, meta)
}
