package validation

import (
	"fmt"
	"net/url"
	"regexp"
	"strings"
	"unicode"

	"go.uber.org/zap"
)

// Validator provides comprehensive input validation
type Validator struct {
	logger *zap.Logger
}

// NewValidator creates a new input validator
func NewValidator(logger *zap.Logger) *Validator {
	return &Validator{
		logger: logger,
	}
}

// ValidationError represents a validation error
type ValidationError struct {
	Field   string `json:"field"`
	Message string `json:"message"`
	Value   string `json:"value,omitempty"`
}

func (e ValidationError) Error() string {
	return fmt.Sprintf("validation error for field '%s': %s", e.Field, e.Message)
}

// ValidationResult holds the result of validation
type ValidationResult struct {
	Valid  bool               `json:"valid"`
	Errors []ValidationError  `json:"errors"`
}

// AddError adds a validation error
func (vr *ValidationResult) AddError(field, message, value string) {
	vr.Valid = false
	vr.Errors = append(vr.Errors, ValidationError{
		Field:   field,
		Message: message,
		Value:   value,
	})
}

// Regular expressions for validation
var (
	emailRegex       = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
	appIDRegex       = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]$`)
	tokenPrefixRegex = regexp.MustCompile(`^[A-Z]{2,4}$`)
	permissionRegex  = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9._]*[a-zA-Z0-9]$`)
)

// ValidateEmail validates email addresses
func (v *Validator) ValidateEmail(email string) *ValidationResult {
	result := &ValidationResult{Valid: true}
	
	if email == "" {
		result.AddError("email", "Email is required", "")
		return result
	}
	
	if len(email) > 254 {
		result.AddError("email", "Email too long (max 254 characters)", email)
		return result
	}
	
	if !emailRegex.MatchString(email) {
		result.AddError("email", "Invalid email format", email)
		return result
	}
	
	// Additional email security checks
	if strings.Contains(email, "..") {
		result.AddError("email", "Email contains consecutive dots", email)
		return result
	}
	
	// Check for potentially dangerous characters
	dangerousChars := []string{"<", ">", "\"", "'", "&", ";", "|", "`"}
	for _, char := range dangerousChars {
		if strings.Contains(email, char) {
			result.AddError("email", "Email contains invalid characters", email)
			return result
		}
	}
	
	return result
}

// ValidateAppID validates application IDs
func (v *Validator) ValidateAppID(appID string) *ValidationResult {
	result := &ValidationResult{Valid: true}
	
	if appID == "" {
		result.AddError("app_id", "Application ID is required", "")
		return result
	}
	
	if len(appID) < 3 || len(appID) > 100 {
		result.AddError("app_id", "Application ID must be between 3 and 100 characters", appID)
		return result
	}
	
	if !appIDRegex.MatchString(appID) {
		result.AddError("app_id", "Application ID must start and end with alphanumeric characters and contain only letters, numbers, dots, hyphens, and underscores", appID)
		return result
	}
	
	// Check for reserved names
	reservedNames := []string{"admin", "root", "system", "internal", "api", "www", "mail", "ftp"}
	for _, reserved := range reservedNames {
		if strings.EqualFold(appID, reserved) {
			result.AddError("app_id", "Application ID cannot be a reserved name", appID)
			return result
		}
	}
	
	return result
}

// ValidateURL validates URLs
func (v *Validator) ValidateURL(urlStr, fieldName string) *ValidationResult {
	result := &ValidationResult{Valid: true}
	
	if urlStr == "" {
		result.AddError(fieldName, "URL is required", "")
		return result
	}
	
	if len(urlStr) > 2000 {
		result.AddError(fieldName, "URL too long (max 2000 characters)", urlStr)
		return result
	}
	
	parsedURL, err := url.Parse(urlStr)
	if err != nil {
		result.AddError(fieldName, "Invalid URL format", urlStr)
		return result
	}
	
	// Validate scheme
	if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
		result.AddError(fieldName, "URL must use http or https scheme", urlStr)
		return result
	}
	
	// Security: Require HTTPS in production (configurable)
	if parsedURL.Scheme != "https" {
		v.logger.Warn("Non-HTTPS URL provided", zap.String("url", urlStr))
		// In strict mode, this would be an error
		// result.AddError(fieldName, "HTTPS is required", urlStr)
	}
	
	// Validate host
	if parsedURL.Host == "" {
		result.AddError(fieldName, "URL must have a valid host", urlStr)
		return result
	}
	
	// Security: Block localhost and private IPs in production
	if v.isPrivateOrLocalhost(parsedURL.Host) {
		result.AddError(fieldName, "URLs pointing to private or localhost addresses are not allowed", urlStr)
		return result
	}
	
	return result
}

// ValidatePermissions validates a list of permissions
func (v *Validator) ValidatePermissions(permissions []string) *ValidationResult {
	result := &ValidationResult{Valid: true}
	
	if len(permissions) == 0 {
		result.AddError("permissions", "At least one permission is required", "")
		return result
	}
	
	if len(permissions) > 50 {
		result.AddError("permissions", "Too many permissions (max 50)", fmt.Sprintf("%d", len(permissions)))
		return result
	}
	
	seen := make(map[string]bool)
	for i, permission := range permissions {
		field := fmt.Sprintf("permissions[%d]", i)
		
		// Check for duplicates
		if seen[permission] {
			result.AddError(field, "Duplicate permission", permission)
			continue
		}
		seen[permission] = true
		
		// Validate individual permission
		if err := v.validateSinglePermission(permission); err != nil {
			result.AddError(field, err.Error(), permission)
		}
	}
	
	return result
}

// ValidateTokenPrefix validates token prefixes
func (v *Validator) ValidateTokenPrefix(prefix string) *ValidationResult {
	result := &ValidationResult{Valid: true}
	
	if prefix == "" {
		// Empty prefix is allowed - will use default
		return result
	}
	
	if len(prefix) < 2 || len(prefix) > 4 {
		result.AddError("token_prefix", "Token prefix must be between 2 and 4 characters", prefix)
		return result
	}
	
	if !tokenPrefixRegex.MatchString(prefix) {
		result.AddError("token_prefix", "Token prefix must contain only uppercase letters", prefix)
		return result
	}
	
	return result
}

// ValidateString validates a general string with length and content constraints
func (v *Validator) ValidateString(value, fieldName string, minLen, maxLen int, allowEmpty bool) *ValidationResult {
	result := &ValidationResult{Valid: true}
	
	if value == "" && !allowEmpty {
		result.AddError(fieldName, fmt.Sprintf("%s is required", fieldName), "")
		return result
	}
	
	if len(value) < minLen {
		result.AddError(fieldName, fmt.Sprintf("%s must be at least %d characters", fieldName, minLen), value)
		return result
	}
	
	if len(value) > maxLen {
		result.AddError(fieldName, fmt.Sprintf("%s must be at most %d characters", fieldName, maxLen), value)
		return result
	}
	
	// Check for control characters and other potentially dangerous characters
	for i, r := range value {
		if unicode.IsControl(r) && r != '\n' && r != '\r' && r != '\t' {
			result.AddError(fieldName, fmt.Sprintf("%s contains invalid control character at position %d", fieldName, i), value)
			return result
		}
	}
	
	// Check for null bytes
	if strings.Contains(value, "\x00") {
		result.AddError(fieldName, fmt.Sprintf("%s contains null bytes", fieldName), value)
		return result
	}
	
	return result
}

// ValidateDuration validates duration strings
func (v *Validator) ValidateDuration(duration, fieldName string) *ValidationResult {
	result := &ValidationResult{Valid: true}
	
	if duration == "" {
		result.AddError(fieldName, "Duration is required", "")
		return result
	}
	
	// Basic duration format validation (Go duration format)
	durationRegex := regexp.MustCompile(`^(\d+(\.\d+)?(ns|us|µs|ms|s|m|h))+$`)
	if !durationRegex.MatchString(duration) {
		result.AddError(fieldName, "Invalid duration format (use Go duration format like '1h', '30m', '5s')", duration)
		return result
	}
	
	return result
}

// Helper methods

func (v *Validator) validateSinglePermission(permission string) error {
	if permission == "" {
		return fmt.Errorf("permission cannot be empty")
	}
	
	if len(permission) > 100 {
		return fmt.Errorf("permission too long (max 100 characters)")
	}
	
	if !permissionRegex.MatchString(permission) {
		return fmt.Errorf("permission must start and end with alphanumeric characters and contain only letters, numbers, dots, and underscores")
	}
	
	// Validate permission hierarchy (dots separate levels)
	parts := strings.Split(permission, ".")
	for i, part := range parts {
		if part == "" {
			return fmt.Errorf("permission level %d is empty", i+1)
		}
		if len(part) > 50 {
			return fmt.Errorf("permission level %d is too long (max 50 characters)", i+1)
		}
	}
	
	if len(parts) > 5 {
		return fmt.Errorf("permission hierarchy too deep (max 5 levels)")
	}
	
	return nil
}

func (v *Validator) isPrivateOrLocalhost(host string) bool {
	// Remove port if present
	if colonIndex := strings.LastIndex(host, ":"); colonIndex != -1 {
		host = host[:colonIndex]
	}
	
	// Check for localhost variants
	localhosts := []string{"localhost", "127.0.0.1", "::1", "0.0.0.0"}
	for _, localhost := range localhosts {
		if strings.EqualFold(host, localhost) {
			return true
		}
	}
	
	// Check for private IP ranges (simplified)
	privateRanges := []string{
		"10.", "192.168.", "172.16.", "172.17.", "172.18.", "172.19.",
		"172.20.", "172.21.", "172.22.", "172.23.", "172.24.", "172.25.",
		"172.26.", "172.27.", "172.28.", "172.29.", "172.30.", "172.31.",
	}
	
	for _, privateRange := range privateRanges {
		if strings.HasPrefix(host, privateRange) {
			return true
		}
	}
	
	return false
}

// ValidateApplicationRequest validates create/update application requests
func (v *Validator) ValidateApplicationRequest(appID, appLink, callbackURL string, permissions []string) []ValidationError {
	var errors []ValidationError
	
	// Validate app ID
	if result := v.ValidateAppID(appID); !result.Valid {
		errors = append(errors, result.Errors...)
	}
	
	// Validate app link URL
	if result := v.ValidateURL(appLink, "app_link"); !result.Valid {
		errors = append(errors, result.Errors...)
	}
	
	// Validate callback URL
	if result := v.ValidateURL(callbackURL, "callback_url"); !result.Valid {
		errors = append(errors, result.Errors...)
	}
	
	// Validate permissions
	if result := v.ValidatePermissions(permissions); !result.Valid {
		errors = append(errors, result.Errors...)
	}
	
	return errors
}