// Package validation provides input validation utilities for security
package validation

import (
	"fmt"
	"path/filepath"
	"regexp"
	"strings"
)

// ValidationRule is a function that validates a string value
type ValidationRule func(value string) error

// Validator provides reusable validation rules
type Validator struct {
	errors []string
}

// NewValidator creates a new validator
func NewValidator() *Validator {
	return &Validator{errors: make([]string, 0)}
}

// Add adds a field to validate with the given rules
func (v *Validator) Add(name, value string, rules ...ValidationRule) {
	for _, rule := range rules {
		if err := rule(value); err != nil {
			v.errors = append(v.errors, fmt.Sprintf("%s: %v", name, err))
		}
	}
}

// Valid returns nil if validation passed, otherwise returns an error
func (v *Validator) Valid() error {
	if len(v.errors) > 0 {
		return fmt.Errorf("validation failed: %s", strings.Join(v.errors, "; "))
	}
	return nil
}

// Common validation rules

// SafeName validates alphanumeric + underscore + hyphen only
var SafeName ValidationRule = func(v string) error {
	if matched, _ := regexp.MatchString(`^[a-zA-Z0-9_-]+$`, v); !matched {
		return fmt.Errorf("must contain only alphanumeric characters, underscores, and hyphens")
	}
	return nil
}

// MaxLength validates maximum string length
func MaxLength(max int) ValidationRule {
	return func(v string) error {
		if len(v) > max {
			return fmt.Errorf("exceeds maximum length of %d", max)
		}
		return nil
	}
}

// MinLength validates minimum string length
func MinLength(min int) ValidationRule {
	return func(v string) error {
		if len(v) < min {
			return fmt.Errorf("must be at least %d characters", min)
		}
		return nil
	}
}

// NoPathTraversal validates no path traversal sequences
var NoPathTraversal ValidationRule = func(v string) error {
	if strings.Contains(v, "..") || strings.Contains(v, "../") || strings.Contains(v, "..\\") {
		return fmt.Errorf("path traversal sequence detected")
	}
	return nil
}

// NoShellMetacharacters validates no shell metacharacters
var NoShellMetacharacters ValidationRule = func(v string) error {
	dangerous := []string{";", "|", "&", "`", "$", "(", ")", "<", ">", "*", "?"}
	for _, char := range dangerous {
		if strings.Contains(v, char) {
			return fmt.Errorf("shell metacharacter '%s' detected", char)
		}
	}
	return nil
}

// NoNullBytes validates no null bytes
var NoNullBytes ValidationRule = func(v string) error {
	if strings.Contains(v, "\x00") {
		return fmt.Errorf("null byte detected")
	}
	return nil
}

// ValidPath validates a path is within a base directory
func ValidPath(basePath string) ValidationRule {
	return func(v string) error {
		cleaned := filepath.Clean(v)
		absPath, err := filepath.Abs(cleaned)
		if err != nil {
			return fmt.Errorf("invalid path: %w", err)
		}
		absBase, err := filepath.Abs(basePath)
		if err != nil {
			return fmt.Errorf("invalid base path: %w", err)
		}
		if !strings.HasPrefix(absPath, absBase) {
			return fmt.Errorf("path escapes base directory")
		}
		return nil
	}
}

// MatchesPattern validates against a regex pattern
func MatchesPattern(pattern, description string) ValidationRule {
	re := regexp.MustCompile(pattern)
	return func(v string) error {
		if !re.MatchString(v) {
			return fmt.Errorf("must match pattern: %s", description)
		}
		return nil
	}
}

// Whitelist validates against a whitelist of allowed values
func Whitelist(allowed ...string) ValidationRule {
	return func(v string) error {
		for _, a := range allowed {
			if v == a {
				return nil
			}
		}
		return fmt.Errorf("value not in whitelist")
	}
}

// Sanitize removes dangerous characters from input
func Sanitize(input string) string {
	// Remove null bytes
	input = strings.ReplaceAll(input, "\x00", "")
	// Remove control characters
	input = strings.ReplaceAll(input, "\r", "")
	return input
}

// ValidateJobName validates a job name is safe
func ValidateJobName(jobName string) error {
	validator := NewValidator()
	validator.Add("job_name", jobName,
		MinLength(1),
		MaxLength(64),
		SafeName,
		NoPathTraversal,
		NoShellMetacharacters,
	)
	return validator.Valid()
}

// ValidateExperimentID validates an experiment ID is safe
func ValidateExperimentID(id string) error {
	validator := NewValidator()
	validator.Add("experiment_id", id,
		MinLength(1),
		MaxLength(128),
		SafeName,
		NoPathTraversal,
	)
	return validator.Valid()
}

// ValidateCommand validates a command string is safe
func ValidateCommand(cmd string) error {
	validator := NewValidator()
	validator.Add("command", cmd,
		MinLength(1),
		MaxLength(1024),
		NoShellMetacharacters,
	)
	return validator.Valid()
}
