package logger

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

func TestNewLogger(t *testing.T) {
	tests := []struct {
		name   string
		config Config
	}{
		{
			name:   "default config",
			config: DefaultConfig(),
		},
		{
			name: "debug level",
			config: Config{
				Level: DebugLevel,
			},
		},
		{
			name: "with module",
			config: Config{
				Level:  InfoLevel,
				Module: "test-module",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			logger := NewLogger(tt.config)
			if logger == nil {
				t.Error("NewLogger returned nil")
			}
		})
	}
}

func TestLogLevels(t *testing.T) {
	var buf bytes.Buffer
	logger := NewLogger(Config{
		Level:  DebugLevel,
		Output: &buf,
	})

	tests := []struct {
		name    string
		logFunc func(string, ...Field)
		level   string
		message string
	}{
		{"debug", logger.Debug, "debug", "debug message"},
		{"info", logger.Info, "info", "info message"},
		{"warn", logger.Warn, "warn", "warn message"},
		{"error", logger.Error, "error", "error message"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			buf.Reset()
			tt.logFunc(tt.message)

			var logEntry map[string]interface{}
			if err := json.Unmarshal(buf.Bytes(), &logEntry); err != nil {
				t.Fatalf("Failed to parse log output: %v", err)
			}

			if logEntry["level"] != tt.level {
				t.Errorf("Expected level %s, got %s", tt.level, logEntry["level"])
			}
			if logEntry["message"] != tt.message {
				t.Errorf("Expected message %s, got %s", tt.message, logEntry["message"])
			}
		})
	}
}

func TestLogLevelFiltering(t *testing.T) {
	var buf bytes.Buffer
	logger := NewLogger(Config{
		Level:  WarnLevel,
		Output: &buf,
	})

	// Debug and Info should be filtered out
	logger.Debug("debug message")
	logger.Info("info message")

	if buf.Len() > 0 {
		t.Error("Debug and Info messages should be filtered when level is Warn")
	}

	// Warn and Error should be logged
	logger.Warn("warn message")
	if buf.Len() == 0 {
		t.Error("Warn message should be logged when level is Warn")
	}

	buf.Reset()
	logger.Error("error message")
	if buf.Len() == 0 {
		t.Error("Error message should be logged when level is Warn")
	}
}

func TestWithModule(t *testing.T) {
	var buf bytes.Buffer
	logger := NewLogger(Config{
		Level:  InfoLevel,
		Output: &buf,
	})

	moduleLogger := logger.WithModule("test-module")
	moduleLogger.Info("test message")

	var logEntry map[string]interface{}
	if err := json.Unmarshal(buf.Bytes(), &logEntry); err != nil {
		t.Fatalf("Failed to parse log output: %v", err)
	}

	if logEntry["module"] != "test-module" {
		t.Errorf("Expected module 'test-module', got %v", logEntry["module"])
	}
}

func TestFieldTypes(t *testing.T) {
	var buf bytes.Buffer
	logger := NewLogger(Config{
		Level:  InfoLevel,
		Output: &buf,
	})

	testTime := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC)
	testDuration := 5 * time.Second

	logger.Info("test message",
		String("string_field", "value"),
		Int("int_field", 42),
		Int64("int64_field", 9999999999),
		Float64("float_field", 3.14),
		Bool("bool_field", true),
		Time("time_field", testTime),
		Duration("duration_field", testDuration),
		Any("any_field", []string{"a", "b"}),
	)

	var logEntry map[string]interface{}
	if err := json.Unmarshal(buf.Bytes(), &logEntry); err != nil {
		t.Fatalf("Failed to parse log output: %v", err)
	}

	if logEntry["string_field"] != "value" {
		t.Errorf("Expected string_field 'value', got %v", logEntry["string_field"])
	}
	if logEntry["int_field"] != float64(42) { // JSON numbers are float64
		t.Errorf("Expected int_field 42, got %v", logEntry["int_field"])
	}
	if logEntry["bool_field"] != true {
		t.Errorf("Expected bool_field true, got %v", logEntry["bool_field"])
	}
}

func TestContextFields(t *testing.T) {
	var buf bytes.Buffer
	logger := NewLogger(Config{
		Level:  InfoLevel,
		Output: &buf,
	})

	logger.Info("test message",
		Operation("add_permission"),
		AccountID("123456"),
		UserEmail("user@example.com"),
		CloudType("AWS"),
		InstanceID("inst-001"),
	)

	var logEntry map[string]interface{}
	if err := json.Unmarshal(buf.Bytes(), &logEntry); err != nil {
		t.Fatalf("Failed to parse log output: %v", err)
	}

	expectedFields := map[string]string{
		"operation":   "add_permission",
		"account_id":  "123456",
		"user_email":  "user@example.com",
		"cloud_type":  "AWS",
		"instance_id": "inst-001",
	}

	for key, expected := range expectedFields {
		if logEntry[key] != expected {
			t.Errorf("Expected %s '%s', got %v", key, expected, logEntry[key])
		}
	}
}

func TestSensitiveDataFiltering(t *testing.T) {
	var buf bytes.Buffer
	logger := NewLogger(Config{
		Level:  InfoLevel,
		Output: &buf,
	})

	// Test various sensitive field names
	sensitiveTests := []string{
		"password",
		"secret",
		"token",
		"access_key",
		"access_key_id",
		"access_key_secret",
		"client_secret",
		"api_key",
		"credential",
		"app_secret",
	}

	for _, fieldName := range sensitiveTests {
		buf.Reset()
		logger.Info("test message", String(fieldName, "sensitive_value"))

		var logEntry map[string]interface{}
		if err := json.Unmarshal(buf.Bytes(), &logEntry); err != nil {
			t.Fatalf("Failed to parse log output for field %s: %v", fieldName, err)
		}

		if logEntry[fieldName] != "[REDACTED]" {
			t.Errorf("Expected %s to be '[REDACTED]', got %v", fieldName, logEntry[fieldName])
		}
	}
}

func TestSensitiveDataFilteringCaseInsensitive(t *testing.T) {
	var buf bytes.Buffer
	logger := NewLogger(Config{
		Level:  InfoLevel,
		Output: &buf,
	})

	// Test case variations
	caseTests := []string{
		"Password",
		"PASSWORD",
		"user_password",
		"UserPassword",
		"API_TOKEN",
		"apiToken",
	}

	for _, fieldName := range caseTests {
		buf.Reset()
		logger.Info("test message", String(fieldName, "sensitive_value"))

		var logEntry map[string]interface{}
		if err := json.Unmarshal(buf.Bytes(), &logEntry); err != nil {
			t.Fatalf("Failed to parse log output for field %s: %v", fieldName, err)
		}

		if logEntry[fieldName] != "[REDACTED]" {
			t.Errorf("Expected %s to be '[REDACTED]', got %v", fieldName, logEntry[fieldName])
		}
	}
}

func TestNonSensitiveDataNotFiltered(t *testing.T) {
	var buf bytes.Buffer
	logger := NewLogger(Config{
		Level:  InfoLevel,
		Output: &buf,
	})

	// These should NOT be filtered
	nonSensitiveTests := []struct {
		field string
		value string
	}{
		{"username", "john"},
		{"email", "john@example.com"},
		{"account_id", "123456"},
		{"operation", "add_permission"},
	}

	for _, tt := range nonSensitiveTests {
		buf.Reset()
		logger.Info("test message", String(tt.field, tt.value))

		var logEntry map[string]interface{}
		if err := json.Unmarshal(buf.Bytes(), &logEntry); err != nil {
			t.Fatalf("Failed to parse log output for field %s: %v", tt.field, err)
		}

		if logEntry[tt.field] != tt.value {
			t.Errorf("Expected %s to be '%s', got %v", tt.field, tt.value, logEntry[tt.field])
		}
	}
}

func TestErrorField(t *testing.T) {
	var buf bytes.Buffer
	logger := NewLogger(Config{
		Level:  InfoLevel,
		Output: &buf,
	})

	testErr := &testError{msg: "test error message"}
	logger.Error("operation failed", Err(testErr))

	var logEntry map[string]interface{}
	if err := json.Unmarshal(buf.Bytes(), &logEntry); err != nil {
		t.Fatalf("Failed to parse log output: %v", err)
	}

	if logEntry["error"] != "test error message" {
		t.Errorf("Expected error 'test error message', got %v", logEntry["error"])
	}
}

type testError struct {
	msg string
}

func (e *testError) Error() string {
	return e.msg
}

func TestParseLevel(t *testing.T) {
	tests := []struct {
		input    string
		expected Level
	}{
		{"debug", DebugLevel},
		{"DEBUG", DebugLevel},
		{"info", InfoLevel},
		{"INFO", InfoLevel},
		{"warn", WarnLevel},
		{"WARN", WarnLevel},
		{"warning", WarnLevel},
		{"error", ErrorLevel},
		{"ERROR", ErrorLevel},
		{"invalid", InfoLevel}, // defaults to info
		{"", InfoLevel},        // defaults to info
	}

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

func TestWithFields(t *testing.T) {
	var buf bytes.Buffer
	logger := NewLogger(Config{
		Level:  InfoLevel,
		Output: &buf,
	})

	contextLogger := logger.WithFields(map[string]interface{}{
		"request_id": "req-123",
		"user_id":    "user-456",
	})

	contextLogger.Info("test message")

	var logEntry map[string]interface{}
	if err := json.Unmarshal(buf.Bytes(), &logEntry); err != nil {
		t.Fatalf("Failed to parse log output: %v", err)
	}

	if logEntry["request_id"] != "req-123" {
		t.Errorf("Expected request_id 'req-123', got %v", logEntry["request_id"])
	}
	if logEntry["user_id"] != "user-456" {
		t.Errorf("Expected user_id 'user-456', got %v", logEntry["user_id"])
	}
}

func TestWithFieldsSensitiveFiltering(t *testing.T) {
	var buf bytes.Buffer
	logger := NewLogger(Config{
		Level:  InfoLevel,
		Output: &buf,
	})

	contextLogger := logger.WithFields(map[string]interface{}{
		"api_token": "secret-token",
		"user_id":   "user-456",
	})

	contextLogger.Info("test message")

	var logEntry map[string]interface{}
	if err := json.Unmarshal(buf.Bytes(), &logEntry); err != nil {
		t.Fatalf("Failed to parse log output: %v", err)
	}

	if logEntry["api_token"] != "[REDACTED]" {
		t.Errorf("Expected api_token '[REDACTED]', got %v", logEntry["api_token"])
	}
	if logEntry["user_id"] != "user-456" {
		t.Errorf("Expected user_id 'user-456', got %v", logEntry["user_id"])
	}
}

func TestTimestampPresent(t *testing.T) {
	var buf bytes.Buffer
	logger := NewLogger(Config{
		Level:  InfoLevel,
		Output: &buf,
	})

	logger.Info("test message")

	var logEntry map[string]interface{}
	if err := json.Unmarshal(buf.Bytes(), &logEntry); err != nil {
		t.Fatalf("Failed to parse log output: %v", err)
	}

	if _, ok := logEntry["time"]; !ok {
		t.Error("Expected 'time' field in log entry")
	}
}

func TestPrettyPrint(t *testing.T) {
	var buf bytes.Buffer
	logger := NewLogger(Config{
		Level:       InfoLevel,
		Output:      &buf,
		PrettyPrint: true,
	})

	logger.Info("test message")

	output := buf.String()
	// Pretty print output should not be valid JSON (it's human-readable)
	var logEntry map[string]interface{}
	err := json.Unmarshal(buf.Bytes(), &logEntry)
	if err == nil {
		// If it parses as JSON, it might still be pretty printed
		// Check for console writer format indicators
		if !strings.Contains(output, "INF") && !strings.Contains(output, "test message") {
			t.Error("Pretty print output should contain readable format")
		}
	}
}
