package backup

import (
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/0xJacky/Nginx-UI/settings"
	"github.com/uozi-tech/cosy"
)

// ValidatePathAccess validates if a given path is within the granted access paths.
// This function ensures that all backup read/write operations are restricted to
// authorized directories only, preventing unauthorized file system access.
//
// Parameters:
//   - path: The file system path to validate
//
// Returns:
//   - error: CosyError if path is not allowed, nil if path is valid
func ValidatePathAccess(path string) error {
	if path == "" {
		return cosy.WrapErrorWithParams(ErrInvalidPath, "path cannot be empty")
	}

	// Clean the path to resolve any relative components like ".." or "."
	cleanPath := filepath.Clean(path)

	// Check if the path is within any of the granted access paths
	for _, allowedPath := range settings.BackupSettings.GrantedAccessPath {
		if allowedPath == "" {
			continue
		}

		// Clean the allowed path as well for consistent comparison
		cleanAllowedPath := filepath.Clean(allowedPath)

		// Check if the path is within the allowed path
		if strings.HasPrefix(cleanPath, cleanAllowedPath) {
			// Ensure it's actually a subdirectory or the same directory
			// This prevents "/tmp" from matching "/tmpfoo"
			if cleanPath == cleanAllowedPath || strings.HasPrefix(cleanPath, cleanAllowedPath+string(filepath.Separator)) {
				return nil
			}
		}
	}

	return cosy.WrapErrorWithParams(ErrPathNotInGrantedAccess, cleanPath)
}

// ValidateBackupPath validates the backup source path for custom directory backups.
// This function checks if the source directory exists and is accessible.
//
// Parameters:
//   - path: The backup source path to validate
//
// Returns:
//   - error: CosyError if validation fails, nil if path is valid
func ValidateBackupPath(path string) error {
	// First check if path is in granted access paths
	if err := ValidatePathAccess(path); err != nil {
		return err
	}

	// Check if the path exists and is a directory
	info, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			return cosy.WrapErrorWithParams(ErrBackupPathNotExist, path)
		}
		return cosy.WrapErrorWithParams(ErrBackupPathAccess, path, err.Error())
	}

	if !info.IsDir() {
		return cosy.WrapErrorWithParams(ErrBackupPathNotDirectory, path)
	}

	return nil
}

// ValidateStoragePath validates the storage destination path for backup files.
// This function ensures the storage directory exists or can be created.
//
// Parameters:
//   - path: The storage destination path to validate
//
// Returns:
//   - error: CosyError if validation fails, nil if path is valid
func ValidateStoragePath(path string) error {
	// First check if path is in granted access paths
	if err := ValidatePathAccess(path); err != nil {
		return err
	}

	// Check if the directory exists, if not try to create it
	if _, err := os.Stat(path); os.IsNotExist(err) {
		if err := os.MkdirAll(path, 0755); err != nil {
			return cosy.WrapErrorWithParams(ErrCreateStorageDir, path, err.Error())
		}
	} else if err != nil {
		return cosy.WrapErrorWithParams(ErrStoragePathAccess, path, err.Error())
	}

	return nil
}

// copyFile copies a file from source to destination with proper error handling.
// This function handles file copying operations used in backup processes.
//
// Parameters:
//   - src: Source file path
//   - dst: Destination file path
//
// Returns:
//   - error: Standard error if copy operation fails
func copyFile(src, dst string) error {
	// Open source file for reading
	source, err := os.Open(src)
	if err != nil {
		return err
	}
	defer source.Close()

	// Create destination file for writing
	destination, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer destination.Close()

	// Copy file content from source to destination
	_, err = io.Copy(destination, source)
	return err
}

// copyDirectory recursively copies a directory from source to destination.
// This function preserves file permissions and handles symbolic links properly.
//
// Parameters:
//   - src: Source directory path
//   - dst: Destination directory path
//
// Returns:
//   - error: CosyError if copy operation fails
func copyDirectory(src, dst string) error {
	// Verify source is a directory
	srcInfo, err := os.Stat(src)
	if err != nil {
		return err
	}
	if !srcInfo.IsDir() {
		return cosy.WrapErrorWithParams(ErrCopyNginxConfigDir, "%s is not a directory", src)
	}

	// Create destination directory with same permissions as source
	if err := os.MkdirAll(dst, srcInfo.Mode()); err != nil {
		return err
	}

	// Walk through source directory and copy all contents
	return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// Calculate relative path from source root
		relPath, err := filepath.Rel(src, path)
		if err != nil {
			return err
		}
		if relPath == "." {
			return nil
		}

		// Construct target path
		targetPath := filepath.Join(dst, relPath)

		// Handle symbolic links by recreating them
		if info.Mode()&os.ModeSymlink != 0 {
			linkTarget, err := os.Readlink(path)
			if err != nil {
				return err
			}
			return os.Symlink(linkTarget, targetPath)
		}

		// Create directories with original permissions
		if info.IsDir() {
			return os.MkdirAll(targetPath, info.Mode())
		}

		// Copy regular files
		return copyFile(path, targetPath)
	})
}
