package security

import (
	"os"
	"strings"
	"testing"

	"github.com/jfraeys/fetch_ml/internal/worker"
)

// TestSecretsEnvExpansionE2E tests environment variable expansion through full config load
func TestSecretsEnvExpansionE2E(t *testing.T) {
	// Set test environment variables
	os.Setenv("TEST_REDIS_PASSWORD", "secret_redis_pass_123")
	os.Setenv("TEST_AWS_ACCESS_KEY", "AKIAIOSFODNN7EXAMPLE")
	os.Setenv("TEST_AWS_SECRET_KEY", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY")
	defer func() {
		os.Unsetenv("TEST_REDIS_PASSWORD")
		os.Unsetenv("TEST_AWS_ACCESS_KEY")
		os.Unsetenv("TEST_AWS_SECRET_KEY")
	}()

	// Create a config with environment variable references
	config := &worker.Config{
		RedisPassword: "${TEST_REDIS_PASSWORD}",
		SnapshotStore: worker.SnapshotStoreConfig{
			AccessKey: "${TEST_AWS_ACCESS_KEY}",
			SecretKey: "${TEST_AWS_SECRET_KEY}",
		},
	}

	// Expand secrets
	config.ExpandSecrets()

	// Verify expansion worked
	if config.RedisPassword != "secret_redis_pass_123" {
		t.Errorf("Redis password not expanded correctly: got %s", config.RedisPassword)
	}
	if config.SnapshotStore.AccessKey != "AKIAIOSFODNN7EXAMPLE" {
		t.Errorf("AWS access key not expanded correctly: got %s", config.SnapshotStore.AccessKey)
	}
	if config.SnapshotStore.SecretKey != "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" {
		t.Errorf("AWS secret key not expanded correctly: got %s", config.SnapshotStore.SecretKey)
	}
}

// TestSecretsPlaintextDetectionE2E tests that plaintext secrets fail validation
func TestSecretsPlaintextDetectionE2E(t *testing.T) {
	tests := []struct {
		name        string
		redisPass   string
		accessKey   string
		secretKey   string
		shouldFail  bool
		description string
	}{
		{
			name:        "plaintext AWS key should fail",
			redisPass:   "${REDIS_PASSWORD}",
			accessKey:   "AKIAIOSFODNN7EXAMPLE",
			secretKey:   "${AWS_SECRET_KEY}",
			shouldFail:  true,
			description: "AWS access key in plaintext should be detected",
		},
		{
			name:        "plaintext high-entropy secret should fail",
			redisPass:   "${REDIS_PASSWORD}",
			accessKey:   "${AWS_ACCESS_KEY}",
			secretKey:   "super_secret_key_with_high_entropy_12345!@#$%",
			shouldFail:  true,
			description: "High entropy secret should be detected as plaintext",
		},
		{
			name:        "env references should pass",
			redisPass:   "${REDIS_PASSWORD}",
			accessKey:   "${AWS_ACCESS_KEY}",
			secretKey:   "${AWS_SECRET_KEY}",
			shouldFail:  false,
			description: "Environment variable references should be allowed",
		},
		{
			name:        "empty values should pass",
			redisPass:   "",
			accessKey:   "",
			secretKey:   "",
			shouldFail:  false,
			description: "Empty values should be allowed",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			config := &worker.Config{
				RedisPassword: tt.redisPass,
				SnapshotStore: worker.SnapshotStoreConfig{
					AccessKey: tt.accessKey,
					SecretKey: tt.secretKey,
				},
			}

			err := config.ValidateNoPlaintextSecrets()
			if tt.shouldFail && err == nil {
				t.Errorf("%s: expected validation to fail but it passed", tt.description)
			}
			if !tt.shouldFail && err != nil {
				t.Errorf("%s: expected validation to pass but got error: %v", tt.description, err)
			}
		})
	}
}

// TestSecretsRotation tests hot-reload capability for secrets
func TestSecretsRotation(t *testing.T) {
	// Initial secret value
	os.Setenv("ROTATING_SECRET", "initial_secret_value")
	defer os.Unsetenv("ROTATING_SECRET")

	// Load config initially
	config1 := &worker.Config{
		RedisPassword: "${ROTATING_SECRET}",
	}
	config1.ExpandSecrets()

	if config1.RedisPassword != "initial_secret_value" {
		t.Fatalf("Initial expansion failed: got %s", config1.RedisPassword)
	}

	// Simulate secret rotation by changing env var
	os.Setenv("ROTATING_SECRET", "rotated_secret_value")

	// Create new config (simulating reload)
	config2 := &worker.Config{
		RedisPassword: "${ROTATING_SECRET}",
	}
	config2.ExpandSecrets()

	if config2.RedisPassword != "rotated_secret_value" {
		t.Errorf("Secret rotation failed: expected 'rotated_secret_value', got '%s'", config2.RedisPassword)
	}

	// Verify old config still has old value (no shared state)
	if config1.RedisPassword != "initial_secret_value" {
		t.Logf("Old config retained initial value as expected: %s", config1.RedisPassword)
	}
}

// TestSecretsPatternDetection validates specific secret pattern detection
func TestSecretsPatternDetection(t *testing.T) {
	patterns := []struct {
		value      string
		shouldMatch bool
		pattern    string
	}{
		// AWS patterns
		{"AKIAIOSFODNN7EXAMPLE", true, "AWS Access Key"},
		{"ASIAIOSFODNN7EXAMPLE", true, "AWS Session Key"},
		{"AKIA1234567890", true, "AWS Access Key short"},
		
		// GitHub patterns  
		{"ghp_xxxxxxxxxxxxxxxxxxxx", true, "GitHub Personal Token"},
		{"gho_xxxxxxxxxxxxxxxxxxxx", true, "GitHub OAuth"},
		{"github_pat_xxxxxxxxxx", true, "GitHub App Token"},
		
		// GitLab patterns
		{"glpat-xxxxxxxxxxxxxxxx", true, "GitLab Token"},
		
		// OpenAI/Stripe patterns
		{"sk-xxxxxxxxxxxxxxxxxxxxxxxx", true, "OpenAI/Stripe Secret"},
		{"sk_test_xxxxxxxxxxxxxx", true, "Stripe Test Key"},
		{"sk_live_xxxxxxxxxxxxxx", true, "Stripe Live Key"},
		
		// Non-secrets that should NOT match
		{"not-a-secret", false, "Plain text"},
		{"password123", false, "Simple password"},
		{"${ENV_VAR}", false, "Env reference"},
		{"AKIA", false, "Too short"}, // Too short to be a real key
		{"", false, "Empty string"},
	}

	for _, p := range patterns {
		t.Run(p.pattern, func(t *testing.T) {
			// Check if the value looks like a secret
			looksLikeSecret := looksLikeSecretHelper(p.value)
			
			if p.shouldMatch && !looksLikeSecret {
				t.Errorf("Expected '%s' to be detected as secret but wasn't", p.value)
			}
			if !p.shouldMatch && looksLikeSecret {
				t.Logf("Note: '%s' was detected as secret (may be acceptable based on entropy)", p.value)
			}
		})
	}
}

// looksLikeSecretHelper mirrors the internal detection logic for testing
func looksLikeSecretHelper(value string) bool {
	if value == "" || strings.HasPrefix(value, "${") {
		return false
	}

	// Check for known secret patterns
	patterns := []string{
		"AKIA",      // AWS Access Key
		"ASIA",      // AWS Session Key
		"ghp_",      // GitHub personal
		"gho_",      // GitHub OAuth
		"github_pat_", // GitHub app
		"glpat-",    // GitLab
		"sk-",       // OpenAI/Stripe
	}

	for _, pattern := range patterns {
		if strings.Contains(value, pattern) {
			return true
		}
	}

	// High entropy check (simplified)
	if len(value) >= 20 {
		return true
	}

	return false
}
