package config

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"gitgud.io/mike/mpv-manager/pkg/constants"
)

// ValidateBackupPath validates that a backup path is within the allowed backup directory
// and has a valid filename format. This prevents path traversal attacks.
func ValidateBackupPath(backupPath string) error {
	// Step 1: Clean path to resolve ".." sequences
	cleanPath := filepath.Clean(backupPath)

	// Step 2: Get expected backup directory
	backupDir, err := constants.GetMPVConfigBackupDirWithHome()
	if err != nil {
		return fmt.Errorf("failed to get backup directory: %w", err)
	}

	// Step 3: Get absolute path for backup directory
	absBackupDir, err := filepath.Abs(backupDir)
	if err != nil {
		return fmt.Errorf("failed to get absolute backup directory")
	}

	// Step 4: Get absolute path for backup and resolve symlinks if file exists
	var absBackupPath string
	fileInfo, err := os.Stat(cleanPath)
	if err == nil && fileInfo.Mode()&os.ModeSymlink != 0 {
		// File is a symlink - resolve it for security
		absBackupPath, err = filepath.EvalSymlinks(cleanPath)
		if err != nil {
			return fmt.Errorf("failed to resolve backup path")
		}
	} else if err == nil {
		// Regular file exists - get absolute path
		absBackupPath, err = filepath.Abs(cleanPath)
		if err != nil {
			return fmt.Errorf("failed to resolve backup path")
		}
	} else if os.IsNotExist(err) {
		// File doesn't exist - get absolute path
		absBackupPath, err = filepath.Abs(cleanPath)
		if err != nil {
			return fmt.Errorf("failed to resolve backup path")
		}
	} else {
		// Other error (permission denied, etc.)
		return fmt.Errorf("failed to resolve backup path")
	}

	// Step 5: Extract and validate filename pattern FIRST
	// Pattern: YYYY-MM-DD-HHMMSS_mpv.conf
	filename := filepath.Base(absBackupPath)
	matched, _ := regexp.MatchString(`^\d{4}-\d{2}-\d{2}-\d{6}_mpv\.conf$`, filename)
	if !matched {
		return fmt.Errorf("invalid backup filename: %s", filename)
	}

	// Step 6: Validate timestamp values
	// Extract timestamp from filename: YYYY-MM-DD-HHMMSS
	timestampStr := filename[:17]                            // "2026-01-31-120000"
	timestampStr = strings.ReplaceAll(timestampStr, "-", "") // "20260131120000"

	// Parse as layout: "20060102150405" (YYYYMMDDHHmmss)
	// Use ParseInLocation with Local to match time.Now() behavior
	timestamp, err := time.ParseInLocation("20060102150405", timestampStr, time.Local)
	if err != nil {
		return fmt.Errorf("invalid timestamp in filename: %s", filename)
	}

	// Validate timestamp is reasonable (not in future, not too old)
	now := time.Now()
	maxFuture := 24 * time.Hour     // Allow 24 hours into future (for timezone differences)
	maxPast := 365 * 24 * time.Hour // Allow 1 year in past

	// Reject timestamps beyond 24 hours in the future
	if timestamp.After(now.Add(maxFuture)) {
		return fmt.Errorf("backup timestamp is too far in the future: %s", filename)
	}

	// Reject timestamps more than 365 days in the past
	// Subtract 1 second from boundary to account for fractional second loss during formatting/parsing
	if timestamp.Before(now.Add(-maxPast - time.Second)) {
		return fmt.Errorf("backup timestamp is too old: %s", filename)
	}

	// Step 7: Validate path is subdirectory (AFTER filename validation to get proper error messages)
	relPath, err := filepath.Rel(absBackupDir, absBackupPath)
	if err != nil || strings.HasPrefix(relPath, "..") {
		return fmt.Errorf("invalid backup path: outside allowed directory")
	}

	return nil
}
