package logging

import (
	"bytes"
	"encoding/json"
	"strings"
	"testing"
)

func TestIsSensitiveField(t *testing.T) {
	tests := []struct {
		fieldName string
		want      bool
	}{
		// Exact matches (should be sensitive)
		{"password", true},
		{"PASSWORD", true},
		{"Password", true},
		{"secret", true},
		{"token", true},
		{"api_key", true},
		{"apikey", true},
		{"api-key", true},
		{"bearer", true},
		{"authorization", true},
		{"auth_token", true},
		{"access_token", true},
		{"refresh_token", true},
		{"private_key", true},
		{"secret_key", true},
		{"client_secret", true},
		{"cookie", true},
		{"session", true},
		{"x-api-key", true},
		{"credentials", true},
		{"credit_card", true},
		{"cvv", true},
		{"ssn", true},
		{"bank_account", true},

		// Pattern matches (contain sensitive keywords)
		{"user_password", true},
		{"db_password", true},
		{"auth_header", true},
		{"secret_value", true},
		{"api_key_id", true},
		{"bearer_token", true},

		// Non-sensitive fields
		{"name", false},
		{"email", false},
		{"user_id", false},
		{"project_id", false},
		{"status", false},
		{"created_at", false},
		{"component", false},
		{"handler", false},
		{"duration_ms", false},
		{"request_id", false},
	}

	for _, tt := range tests {
		t.Run(tt.fieldName, func(t *testing.T) {
			got := IsSensitiveField(tt.fieldName)
			if got != tt.want {
				t.Errorf("IsSensitiveField(%q) = %v, want %v", tt.fieldName, got, tt.want)
			}
		})
	}
}

func TestRedactValue(t *testing.T) {
	tests := []struct {
		name  string
		value string
		want  string
	}{
		// Bearer tokens
		{
			name:  "bearer token",
			value: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
			want:  RedactedValue,
		},
		// AWS access keys
		{
			name:  "aws access key",
			value: "AKIAIOSFODNN7EXAMPLE",
			want:  RedactedValue,
		},
		// JWT tokens
		{
			name:  "jwt token",
			value: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U",
			want:  RedactedValue,
		},
		// Connection strings with passwords
		{
			name:  "postgres connection string",
			value: "postgres://user:secretpassword123@localhost:5432/mydb",
			want:  RedactedValue,
		},
		{
			name:  "redis connection string",
			value: "redis://admin:p@ssw0rd@redis.example.com:6379",
			want:  RedactedValue,
		},
		// Private key header
		{
			name:  "private key",
			value: "-----BEGIN RSA PRIVATE KEY-----",
			want:  RedactedValue,
		},
		// API key patterns
		{
			name:  "api key in string",
			value: "api_key=abc123xyz456789012345678901234567890",
			want:  RedactedValue,
		},
		// Non-sensitive values should pass through
		{
			name:  "normal string",
			value: "hello world",
			want:  "hello world",
		},
		{
			name:  "email",
			value: "user@example.com",
			want:  "user@example.com",
		},
		{
			name:  "uuid",
			value: "550e8400-e29b-41d4-a716-446655440000",
			want:  "550e8400-e29b-41d4-a716-446655440000",
		},
		{
			name:  "path",
			value: "/api/v1/projects/123",
			want:  "/api/v1/projects/123",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := RedactValue(tt.value)
			if got != tt.want {
				t.Errorf("RedactValue(%q) = %q, want %q", tt.value, got, tt.want)
			}
		})
	}
}

func TestContainsSensitiveData(t *testing.T) {
	tests := []struct {
		name  string
		value string
		want  bool
	}{
		{"bearer token", "Authorization: Bearer xyz123", true},
		{"jwt", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIn0.abc", true},
		{"normal text", "This is a normal log message", false},
		{"empty", "", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := ContainsSensitiveData(tt.value)
			if got != tt.want {
				t.Errorf("ContainsSensitiveData(%q) = %v, want %v", tt.value, got, tt.want)
			}
		})
	}
}

func TestRedactingHandlerFieldNames(t *testing.T) {
	var buf bytes.Buffer
	cfg := DefaultConfig()
	cfg.RedactEnabled = true
	logger := NewWithWriter(cfg, &buf)

	// Log with sensitive field names
	logger.Info("test",
		"password", "secret123",
		"api_key", "key456",
		"name", "visible",
	)

	output := buf.String()

	// Sensitive fields should be redacted
	if strings.Contains(output, "secret123") {
		t.Errorf("password value should be redacted, got: %s", output)
	}
	if strings.Contains(output, "key456") {
		t.Errorf("api_key value should be redacted, got: %s", output)
	}
	// Non-sensitive fields should be visible
	if !strings.Contains(output, "visible") {
		t.Errorf("non-sensitive field should be visible, got: %s", output)
	}
	// Redacted marker should appear
	if !strings.Contains(output, RedactedValue) {
		t.Errorf("expected redacted marker in output, got: %s", output)
	}
}

func TestRedactingHandlerValues(t *testing.T) {
	var buf bytes.Buffer
	cfg := DefaultConfig()
	cfg.RedactEnabled = true
	logger := NewWithWriter(cfg, &buf)

	// Log with sensitive values
	logger.Info("test",
		"auth_header", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIn0.abc",
	)

	output := buf.String()

	// JWT should be redacted
	if strings.Contains(output, "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9") {
		t.Errorf("JWT token should be redacted, got: %s", output)
	}
}

func TestRedactingHandlerPreservesStructure(t *testing.T) {
	var buf bytes.Buffer
	cfg := DefaultConfig()
	cfg.Format = FormatJSON
	cfg.RedactEnabled = true
	logger := NewWithWriter(cfg, &buf)

	logger.Info("test message",
		"user_id", "123",
		"password", "secret",
		"status", "success",
	)

	// Parse JSON to verify structure is preserved
	var entry map[string]any
	if err := json.Unmarshal(buf.Bytes(), &entry); err != nil {
		t.Fatalf("expected valid JSON, got error: %v, output: %s", err, buf.String())
	}

	// Check that fields exist
	if entry["user_id"] != "123" {
		t.Errorf("expected user_id=123, got: %v", entry["user_id"])
	}
	if entry["status"] != "success" {
		t.Errorf("expected status=success, got: %v", entry["status"])
	}
	// Password should be redacted
	if entry["password"] != RedactedValue {
		t.Errorf("expected password to be redacted, got: %v", entry["password"])
	}
}

func TestRedactingHandlerDisabled(t *testing.T) {
	var buf bytes.Buffer
	cfg := DefaultConfig()
	cfg.RedactEnabled = false
	logger := NewWithWriter(cfg, &buf)

	// With redaction disabled, sensitive values should pass through
	logger.Info("test", "password", "visible_password")

	output := buf.String()
	if !strings.Contains(output, "visible_password") {
		t.Errorf("with redaction disabled, password should be visible, got: %s", output)
	}
}

// TestSecurityCriticalPatterns tests patterns that MUST be redacted
func TestSecurityCriticalPatterns(t *testing.T) {
	criticalPatterns := []struct {
		name  string
		value string
	}{
		{"AWS access key", "AKIAIOSFODNN7EXAMPLE"},
		{"Private key header", "-----BEGIN RSA PRIVATE KEY-----"},
		{"Bearer token", "Bearer sk-1234567890abcdef"},
		{"Postgres connection", "postgres://admin:secret@db:5432/app"},
		{"MongoDB connection", "mongodb://user:pass@mongo:27017/db"},
		{"Redis connection", "redis://:password@redis:6379"},
	}

	for _, tt := range criticalPatterns {
		t.Run(tt.name, func(t *testing.T) {
			var buf bytes.Buffer
			cfg := DefaultConfig()
			cfg.RedactEnabled = true
			logger := NewWithWriter(cfg, &buf)

			logger.Info("test", "data", tt.value)

			output := buf.String()
			if strings.Contains(output, tt.value) && !strings.Contains(tt.value, RedactedValue) {
				t.Errorf("SECURITY: %s should be redacted, but found in output: %s", tt.name, output)
			}
		})
	}
}
