package validator

import (
	"strings"
	"testing"
)

func TestCalculateShannonEntropy(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		minBits  float64
		maxBits  float64
		expected string
	}{
		{
			name:     "Empty string",
			input:    "",
			minBits:  0,
			maxBits:  0,
			expected: "should have 0 entropy",
		},
		{
			name:     "All same character",
			input:    "aaaaaaaaaa",
			minBits:  0,
			maxBits:  0,
			expected: "should have very low entropy",
		},
		{
			name:     "Low entropy - repeated pattern",
			input:    "abcabcabcabc",
			minBits:  1.5,
			maxBits:  2.0,
			expected: "should have low entropy",
		},
		{
			name:     "Medium entropy - simple password",
			input:    "Password123",
			minBits:  3.0,
			maxBits:  4.5,
			expected: "should have medium entropy",
		},
		{
			name:     "High entropy - random base64",
			input:    "j8EJm9/ZKnuTYxcVKQK/NWcrt1Drgzx",
			minBits:  4.0,
			maxBits:  6.0,
			expected: "should have high entropy",
		},
		{
			name:     "Very high entropy - long random base64",
			input:    "PKiQCYBT+AxkksUbC+F5NJsQBG+GDRvlc/5d+240xljW2uVtzsz0uqv0sjCJFirR",
			minBits:  4.5,
			maxBits:  6.5,
			expected: "should have very high entropy",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			entropy := calculateShannonEntropy(tt.input)
			if entropy < tt.minBits || entropy > tt.maxBits {
				t.Errorf("%s: got %.2f bits/char, expected between %.1f and %.1f", tt.expected, entropy, tt.minBits, tt.maxBits)
			}
		})
	}
}

func TestHasSufficientComplexity(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected bool
	}{
		{
			name:     "Empty string",
			input:    "",
			expected: false,
		},
		{
			name:     "Only lowercase",
			input:    "abcdefghijklmnop",
			expected: false,
		},
		{
			name:     "Only uppercase",
			input:    "ABCDEFGHIJKLMNOP",
			expected: false,
		},
		{
			name:     "Only digits",
			input:    "1234567890",
			expected: false,
		},
		{
			name:     "Lowercase + uppercase",
			input:    "AbCdEfGhIjKl",
			expected: false,
		},
		{
			name:     "Lowercase + digits",
			input:    "abc123def456",
			expected: false,
		},
		{
			name:     "Uppercase + digits",
			input:    "ABC123DEF456",
			expected: false,
		},
		{
			name:     "Lowercase + uppercase + digits",
			input:    "Abc123Def456",
			expected: true,
		},
		{
			name:     "Lowercase + uppercase + special",
			input:    "AbC+DeF/GhI=",
			expected: true,
		},
		{
			name:     "Lowercase + digits + special",
			input:    "abc123+def456/",
			expected: true,
		},
		{
			name:     "All four types",
			input:    "Abc123+Def456/",
			expected: true,
		},
		{
			name:     "Base64 string",
			input:    "K8vN2mP9sQ4tR7wY3zA6b+xK8vN2mP9sQ4tR7wY3zA6b=",
			expected: true,
		},
	}

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

func TestCheckRepeatingPatterns(t *testing.T) {
	tests := []struct {
		name      string
		input     string
		shouldErr bool
	}{
		{
			name:      "Empty string",
			input:     "",
			shouldErr: false,
		},
		{
			name:      "Single character",
			input:     "a",
			shouldErr: false,
		},
		{
			name:      "No repeating",
			input:     "abcdefgh",
			shouldErr: false,
		},
		{
			name:      "Two repeating (ok)",
			input:     "aabcdeef",
			shouldErr: false,
		},
		{
			name:      "Three repeating (ok)",
			input:     "aaabcdeee",
			shouldErr: false,
		},
		{
			name:      "Four repeating (error)",
			input:     "aaaabcde",
			shouldErr: true,
		},
		{
			name:      "Five repeating (error)",
			input:     "aaaaabcde",
			shouldErr: true,
		},
		{
			name:      "Multiple groups of three (ok)",
			input:     "aaabbbccc",
			shouldErr: false,
		},
		{
			name:      "Repeating in middle (error)",
			input:     "abcdddddef",
			shouldErr: true,
		},
		{
			name:      "Repeating at end (error)",
			input:     "abcdefgggg",
			shouldErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := checkRepeatingPatterns(tt.input)
			if (err != nil) != tt.shouldErr {
				t.Errorf("checkRepeatingPatterns(%q) error = %v, shouldErr = %v", tt.input, err, tt.shouldErr)
			}
		})
	}
}

func TestHasSequentialPattern(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected bool
	}{
		{
			name:     "Empty string",
			input:    "",
			expected: false,
		},
		{
			name:     "Too short",
			input:    "abc",
			expected: false,
		},
		{
			name:     "No sequential",
			input:    "acegikmo",
			expected: false,
		},
		{
			name:     "Ascending sequence - abcd",
			input:    "xyzabcdefg",
			expected: true,
		},
		{
			name:     "Descending sequence - dcba",
			input:    "xyzdcbafg",
			expected: true,
		},
		{
			name:     "Ascending digits - 1234",
			input:    "abc1234def",
			expected: true,
		},
		{
			name:     "Descending digits - 4321",
			input:    "abc4321def",
			expected: true,
		},
		{
			name:     "Random characters",
			input:    "xK8vN2mP9sQ4",
			expected: false,
		},
		{
			name:     "Base64-like",
			input:    "K8vN2mP9sQ4tR7wY3zA6b",
			expected: false,
		},
	}

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

func TestLooksLikeBase64(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected bool
	}{
		{
			name:     "Empty string",
			input:    "",
			expected: false,
		},
		{
			name:     "Too short",
			input:    "abc",
			expected: false,
		},
		{
			name:     "Only lowercase",
			input:    "abcdefghijklmnopqrstuvwxyzabcdef",
			expected: false,
		},
		{
			name:     "Real base64",
			input:    "K8vN2mP9sQ4tR7wY3zA6bxK8vN2mP9sQ4tR7wY3zA6b=",
			expected: true,
		},
		{
			name:     "Base64 without padding",
			input:    "K8vN2mP9sQ4tR7wY3zA6bxK8vN2mP9sQ4tR7wY3zA6b",
			expected: true,
		},
		{
			name:     "Base64 with URL-safe chars",
			input:    "K8vN2mP9sQ4tR7wY3zA6bxK8vN2mP9sQ4tR7wY3zA6b-_",
			expected: true,
		},
		{
			name:     "Generated secret",
			input:    "xK8vN2mP9sQ4tR7wY3zA6bxK8vN2mP9sQ4tR7wY3zA6bxK8vN2mP9sQ4tR7wY3zA6b",
			expected: true,
		},
		{
			name:     "Simple password",
			input:    "Password123!Password123!Password123!",
			expected: false,
		},
	}

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

func TestValidateJWTSecret(t *testing.T) {
	validator := NewCredentialValidator()

	tests := []struct {
		name        string
		secret      string
		environment string
		shouldErr   bool
		errContains string
	}{
		{
			name:        "Too short - 20 chars",
			secret:      "12345678901234567890",
			environment: "development",
			shouldErr:   true,
			errContains: "too short",
		},
		{
			name:        "Minimum length - 32 chars (acceptable for dev)",
			secret:      "j8EJm9/ZKnuTYxcVKQK/NWcrt1Drgzx",
			environment: "development",
			shouldErr:   false,
		},
		{
			name:        "Common weak secret - contains password",
			secret:      "my-password-is-secure-123456789012",
			environment: "development",
			shouldErr:   true,
			errContains: "common weak value",
		},
		{
			name:        "Common weak secret - secret",
			secret:      "secretsecretsecretsecretsecretsec",
			environment: "development",
			shouldErr:   true,
			errContains: "common weak value",
		},
		{
			name:        "Common weak secret - contains 12345",
			secret:      "abcd12345efghijklmnopqrstuvwxyz",
			environment: "development",
			shouldErr:   true,
			errContains: "common weak value",
		},
		{
			name:        "Dangerous pattern - change",
			secret:      "please-change-this-j8EJm9ZKnuTYxcVK",
			environment: "development",
			shouldErr:   true,
			errContains: "suspicious pattern",
		},
		{
			name:        "Dangerous pattern - sample",
			secret:      "sample-secret-j8EJm9ZKnuTYxcVKQ",
			environment: "development",
			shouldErr:   true,
			errContains: "suspicious pattern",
		},
		{
			name:        "Repeating characters",
			secret:      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
			environment: "development",
			shouldErr:   true,
			errContains: "consecutive repeating characters",
		},
		{
			name:        "Sequential pattern - abcd",
			secret:      "abcdefghijklmnopqrstuvwxyzabcdef",
			environment: "development",
			shouldErr:   true,
			errContains: "sequential patterns",
		},
		{
			name:        "Sequential pattern - 1234",
			secret:      "12345678901234567890123456789012",
			environment: "development",
			shouldErr:   true,
			errContains: "sequential patterns",
		},
		{
			name:        "Low entropy secret",
			secret:      "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpP",
			environment: "development",
			shouldErr:   true,
			errContains: "insufficient entropy",
		},
		{
			name:        "Good secret - base64 style (dev)",
			secret:      "j8EJm9/ZKnuTYxcVKQK/NWcrt1Drgzx",
			environment: "development",
			shouldErr:   false,
		},
		{
			name:        "Good secret - longer (dev)",
			secret:      "PKiQCYBT+AxkksUbC+F5NJsQBG+GDRvlc/5d+240xljW2uVtzsz0uqv0sjCJFirR",
			environment: "development",
			shouldErr:   false,
		},
		{
			name:        "Production - too short (32 chars)",
			secret:      "j8EJm9/ZKnuTYxcVKQK/NWcrt1Drgzx",
			environment: "production",
			shouldErr:   true,
			errContains: "too short for production",
		},
		{
			name:        "Production - insufficient complexity",
			secret:      "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01",
			environment: "production",
			shouldErr:   true,
			errContains: "insufficient complexity",
		},
		{
			name:        "Production - low entropy pattern",
			secret:      strings.Repeat("AbCd12!@", 8), // 64 chars but repetitive
			environment: "production",
			shouldErr:   true,
			errContains: "insufficient entropy",
		},
		{
			name:        "Production - good secret",
			secret:      "PKiQCYBT+AxkksUbC+F5NJsQBG+GDRvlc/5d+240xljW2uVtzsz0uqv0sjCJFirR",
			environment: "production",
			shouldErr:   false,
		},
		{
			name:        "Production - excellent secret with padding",
			secret:      "7mK2nP8sR4wT6xZ3bA5cxK7mN1oQ9uS4vY2zA6bxK7mN1oQ9uS4vY2zA6b+W0E=",
			environment: "production",
			shouldErr:   false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := validator.ValidateJWTSecret(tt.secret, tt.environment)

			if tt.shouldErr {
				if err == nil {
					t.Errorf("ValidateJWTSecret() expected error containing %q, got no error", tt.errContains)
				} else if !strings.Contains(err.Error(), tt.errContains) {
					t.Errorf("ValidateJWTSecret() error = %q, should contain %q", err.Error(), tt.errContains)
				}
			} else {
				if err != nil {
					t.Errorf("ValidateJWTSecret() unexpected error: %v", err)
				}
			}
		})
	}
}

func TestValidateJWTSecret_EdgeCases(t *testing.T) {
	validator := NewCredentialValidator()

	t.Run("Secret with mixed weak patterns", func(t *testing.T) {
		secret := "password123admin" // Contains multiple weak patterns
		err := validator.ValidateJWTSecret(secret, "development")
		if err == nil {
			t.Error("Expected error for secret containing weak patterns, got nil")
		}
	})

	t.Run("Secret exactly at minimum length", func(t *testing.T) {
		// 32 characters exactly
		secret := "j8EJm9/ZKnuTYxcVKQK/NWcrt1Drgzx"
		err := validator.ValidateJWTSecret(secret, "development")
		if err != nil {
			t.Errorf("Expected no error for 32-char secret in development, got: %v", err)
		}
	})

	t.Run("Secret exactly at recommended length", func(t *testing.T) {
		// 64 characters exactly - using real random base64
		secret := "PKiQCYBT+AxkksUbC+F5NJsQBG+GDRvlc/5d+240xljW2uVtzsz0uqv0sjCJFir"
		err := validator.ValidateJWTSecret(secret, "production")
		if err != nil {
			t.Errorf("Expected no error for 64-char secret in production, got: %v", err)
		}
	})
}

// Benchmark tests to ensure validation is performant
func BenchmarkCalculateShannonEntropy(b *testing.B) {
	secret := "PKiQCYBT+AxkksUbC+F5NJsQBG+GDRvlc/5d+240xljW2uVtzsz0uqv0sjCJFirR"
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		calculateShannonEntropy(secret)
	}
}

func BenchmarkValidateJWTSecret(b *testing.B) {
	validator := NewCredentialValidator()
	secret := "PKiQCYBT+AxkksUbC+F5NJsQBG+GDRvlc/5d+240xljW2uVtzsz0uqv0sjCJFirR"
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = validator.ValidateJWTSecret(secret, "production")
	}
}
