package validator

import (
	"fmt"
	"math"
	"strings"
	"unicode"

	"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/config"
)

const (
	// MinJWTSecretLength is the minimum required length for JWT secrets (256 bits)
	MinJWTSecretLength = 32

	// RecommendedJWTSecretLength is the recommended length for JWT secrets (512 bits)
	RecommendedJWTSecretLength = 64

	// MinEntropyBits is the minimum Shannon entropy in bits per character
	// For reference: random base64 has ~6 bits/char, we require minimum 4.0
	MinEntropyBits = 4.0

	// MinProductionEntropyBits is the minimum entropy required for production
	MinProductionEntropyBits = 4.5

	// MaxRepeatingCharacters is the maximum allowed consecutive repeating characters
	MaxRepeatingCharacters = 3
)

// WeakSecrets contains common weak/default secrets that should never be used
var WeakSecrets = []string{
	"secret",
	"password",
	"changeme",
	"change-me",
	"change_me",
	"12345",
	"123456",
	"1234567",
	"12345678",
	"123456789",
	"1234567890",
	"default",
	"test",
	"testing",
	"admin",
	"administrator",
	"root",
	"qwerty",
	"qwertyuiop",
	"letmein",
	"welcome",
	"monkey",
	"dragon",
	"master",
	"sunshine",
	"princess",
	"football",
	"starwars",
	"baseball",
	"superman",
	"iloveyou",
	"trustno1",
	"hello",
	"abc123",
	"password123",
	"admin123",
	"guest",
	"user",
	"demo",
	"sample",
	"example",
}

// DangerousPatterns contains patterns that indicate a secret should be changed
var DangerousPatterns = []string{
	"change",
	"replace",
	"update",
	"modify",
	"sample",
	"example",
	"todo",
	"fixme",
	"temp",
	"temporary",
}

// CredentialValidator validates credentials and secrets for security issues
type CredentialValidator interface {
	ValidateJWTSecret(secret string, environment string) error
	ValidateAllCredentials(cfg *config.Config) error
}

type credentialValidator struct{}

// NewCredentialValidator creates a new credential validator
func NewCredentialValidator() CredentialValidator {
	return &credentialValidator{}
}

// ValidateJWTSecret validates JWT secret strength and security
// CWE-798: Comprehensive validation to prevent hard-coded/weak credentials
func (v *credentialValidator) ValidateJWTSecret(secret string, environment string) error {
	// Check minimum length
	if len(secret) < MinJWTSecretLength {
		return fmt.Errorf(
			"JWT secret is too short (%d characters). Minimum required: %d characters (256 bits). "+
				"Generate a secure secret with: openssl rand -base64 64",
			len(secret),
			MinJWTSecretLength,
		)
	}

	// Check for common weak secrets (case-insensitive)
	secretLower := strings.ToLower(secret)
	for _, weak := range WeakSecrets {
		if secretLower == weak || strings.Contains(secretLower, weak) {
			return fmt.Errorf(
				"JWT secret cannot contain common weak value: '%s'. "+
					"Generate a secure secret with: openssl rand -base64 64",
				weak,
			)
		}
	}

	// Check for dangerous patterns indicating default/placeholder values
	for _, pattern := range DangerousPatterns {
		if strings.Contains(secretLower, pattern) {
			return fmt.Errorf(
				"JWT secret contains suspicious pattern '%s' which suggests it's a placeholder. "+
					"Generate a secure secret with: openssl rand -base64 64",
				pattern,
			)
		}
	}

	// Check for repeating character patterns (e.g., "aaaa", "1111")
	if err := checkRepeatingPatterns(secret); err != nil {
		return fmt.Errorf(
			"JWT secret validation failed: %s. "+
				"Generate a secure secret with: openssl rand -base64 64",
			err.Error(),
		)
	}

	// Check for sequential patterns (e.g., "abcd", "1234")
	if hasSequentialPattern(secret) {
		return fmt.Errorf(
			"JWT secret contains sequential patterns (e.g., 'abcd', '1234') which reduces entropy. "+
				"Generate a secure secret with: openssl rand -base64 64",
		)
	}

	// Calculate Shannon entropy
	entropy := calculateShannonEntropy(secret)
	minEntropy := MinEntropyBits
	if environment == "production" {
		minEntropy = MinProductionEntropyBits
	}

	if entropy < minEntropy {
		return fmt.Errorf(
			"JWT secret has insufficient entropy: %.2f bits/char (minimum: %.1f bits/char for %s). "+
				"The secret appears to have low randomness. "+
				"Generate a secure secret with: openssl rand -base64 64",
			entropy,
			minEntropy,
			environment,
		)
	}

	// In production, enforce stricter requirements
	if environment == "production" {
		// Check recommended length for production
		if len(secret) < RecommendedJWTSecretLength {
			return fmt.Errorf(
				"JWT secret is too short for production environment (%d characters). "+
					"Recommended: %d characters (512 bits). "+
					"Generate a secure secret with: openssl rand -base64 64",
				len(secret),
				RecommendedJWTSecretLength,
			)
		}

		// Check for sufficient character complexity
		if !hasSufficientComplexity(secret) {
			return fmt.Errorf(
				"JWT secret has insufficient complexity for production. It should contain a mix of uppercase, lowercase, " +
					"digits, and special characters (at least 3 types). Generate a secure secret with: openssl rand -base64 64",
			)
		}

		// Validate base64-like characteristics (recommended generation method)
		if !looksLikeBase64(secret) {
			return fmt.Errorf(
				"JWT secret does not appear to be randomly generated (expected base64-like characteristics). "+
					"Generate a secure secret with: openssl rand -base64 64",
			)
		}
	}

	return nil
}

// ValidateAllCredentials validates all credentials in the configuration
func (v *credentialValidator) ValidateAllCredentials(cfg *config.Config) error {
	var errors []string

	// Validate JWT Secret
	if err := v.ValidateJWTSecret(cfg.App.JWTSecret, cfg.App.Environment); err != nil {
		errors = append(errors, fmt.Sprintf("JWT Secret validation failed: %s", err.Error()))
	}

	// In production, ensure other critical configs are not using defaults/placeholders
	if cfg.App.Environment == "production" {
		// Check Meilisearch API key
		if cfg.Meilisearch.APIKey == "" {
			errors = append(errors, "Meilisearch API key must be set in production")
		} else if containsDangerousPattern(cfg.Meilisearch.APIKey) {
			errors = append(errors, "Meilisearch API key appears to be a placeholder/default value")
		}

		// Check database hosts are not using localhost
		for _, host := range cfg.Database.Hosts {
			if strings.Contains(strings.ToLower(host), "localhost") || host == "127.0.0.1" {
				errors = append(errors, "Database hosts should not use localhost in production")
				break
			}
		}

		// Check cache host is not localhost
		if strings.Contains(strings.ToLower(cfg.Cache.Host), "localhost") || cfg.Cache.Host == "127.0.0.1" {
			errors = append(errors, "Cache host should not use localhost in production")
		}
	}

	if len(errors) > 0 {
		return fmt.Errorf("credential validation failed:\n  - %s", strings.Join(errors, "\n  - "))
	}

	return nil
}

// calculateShannonEntropy calculates the Shannon entropy of a string in bits per character
// Shannon entropy measures the randomness/unpredictability of data
// Formula: H(X) = -Σ(p(x) * log2(p(x))) where p(x) is the probability of character x
func calculateShannonEntropy(s string) float64 {
	if len(s) == 0 {
		return 0
	}

	// Count character frequencies
	frequencies := make(map[rune]int)
	for _, char := range s {
		frequencies[char]++
	}

	// Calculate entropy
	var entropy float64
	length := float64(len(s))

	for _, count := range frequencies {
		probability := float64(count) / length
		entropy -= probability * math.Log2(probability)
	}

	return entropy
}

// hasSufficientComplexity checks if the secret has a good mix of character types
// Requires at least 3 out of 4 character types for production
func hasSufficientComplexity(secret string) bool {
	var (
		hasUpper   bool
		hasLower   bool
		hasDigit   bool
		hasSpecial bool
	)

	for _, char := range secret {
		switch {
		case unicode.IsUpper(char):
			hasUpper = true
		case unicode.IsLower(char):
			hasLower = true
		case unicode.IsDigit(char):
			hasDigit = true
		default:
			hasSpecial = true
		}
	}

	// Require at least 3 out of 4 character types
	count := 0
	if hasUpper {
		count++
	}
	if hasLower {
		count++
	}
	if hasDigit {
		count++
	}
	if hasSpecial {
		count++
	}

	return count >= 3
}

// checkRepeatingPatterns checks for excessive repeating characters
func checkRepeatingPatterns(s string) error {
	if len(s) < 2 {
		return nil
	}

	repeatCount := 1
	lastChar := rune(s[0])

	for _, char := range s[1:] {
		if char == lastChar {
			repeatCount++
			if repeatCount > MaxRepeatingCharacters {
				return fmt.Errorf(
					"contains %d consecutive repeating characters ('%c'), maximum allowed: %d",
					repeatCount,
					lastChar,
					MaxRepeatingCharacters,
				)
			}
		} else {
			repeatCount = 1
			lastChar = char
		}
	}

	return nil
}

// hasSequentialPattern detects common sequential patterns
func hasSequentialPattern(s string) bool {
	if len(s) < 4 {
		return false
	}

	// Check for at least 4 consecutive sequential characters
	for i := 0; i < len(s)-3; i++ {
		// Check ascending sequence (e.g., "abcd", "1234")
		if s[i+1] == s[i]+1 && s[i+2] == s[i]+2 && s[i+3] == s[i]+3 {
			return true
		}
		// Check descending sequence (e.g., "dcba", "4321")
		if s[i+1] == s[i]-1 && s[i+2] == s[i]-2 && s[i+3] == s[i]-3 {
			return true
		}
	}

	return false
}

// looksLikeBase64 checks if the string has base64-like characteristics
// Base64 uses: A-Z, a-z, 0-9, +, /, and = for padding
func looksLikeBase64(s string) bool {
	if len(s) < MinJWTSecretLength {
		return false
	}

	var (
		hasUpper   bool
		hasLower   bool
		hasDigit   bool
		validChars int
	)

	// Base64 valid characters
	for _, char := range s {
		switch {
		case char >= 'A' && char <= 'Z':
			hasUpper = true
			validChars++
		case char >= 'a' && char <= 'z':
			hasLower = true
			validChars++
		case char >= '0' && char <= '9':
			hasDigit = true
			validChars++
		case char == '+' || char == '/' || char == '=' || char == '-' || char == '_':
			validChars++
		default:
			// Invalid character for base64
			return false
		}
	}

	// Should have good mix of character types typical of base64
	charTypesCount := 0
	if hasUpper {
		charTypesCount++
	}
	if hasLower {
		charTypesCount++
	}
	if hasDigit {
		charTypesCount++
	}

	// Base64 typically has at least uppercase, lowercase, and digits
	// Also check that it doesn't look like a repeated pattern
	if charTypesCount < 3 {
		return false
	}

	// Check for repeated patterns (e.g., "AbCd12!@" repeated)
	// If the string has low unique character count relative to its length, it's probably not random
	uniqueChars := make(map[rune]bool)
	for _, char := range s {
		uniqueChars[char] = true
	}

	// Random base64 should have at least 50% unique characters for strings over 32 chars
	uniqueRatio := float64(len(uniqueChars)) / float64(len(s))
	return uniqueRatio >= 0.4 // At least 40% unique characters
}

// containsDangerousPattern checks if a string contains any dangerous patterns
func containsDangerousPattern(value string) bool {
	valueLower := strings.ToLower(value)
	for _, pattern := range DangerousPatterns {
		if strings.Contains(valueLower, pattern) {
			return true
		}
	}
	return false
}
