package logger

import (
	"context"
	"testing"

	"git.dcentral.systems/toolz/goplt/pkg/logger"
	"go.uber.org/zap"
)

func TestNewZapLogger_JSONFormat(t *testing.T) {
	t.Parallel()

	log, err := NewZapLogger("info", "json")
	if err != nil {
		t.Fatalf("NewZapLogger failed: %v", err)
	}

	if log == nil {
		t.Fatal("NewZapLogger returned nil")
	}

	// Verify it implements the interface (compile-time check)
	_ = log

	// Test that it can log
	log.Info("test message")
}

func TestNewZapLogger_ConsoleFormat(t *testing.T) {
	t.Parallel()

	log, err := NewZapLogger("info", "console")
	if err != nil {
		t.Fatalf("NewZapLogger failed: %v", err)
	}

	if log == nil {
		t.Fatal("NewZapLogger returned nil")
	}

	// Test that it can log
	log.Info("test message")
}

func TestNewZapLogger_InvalidLevel(t *testing.T) {
	t.Parallel()

	log, err := NewZapLogger("invalid", "json")
	if err != nil {
		t.Fatalf("NewZapLogger failed: %v", err)
	}

	if log == nil {
		t.Fatal("NewZapLogger returned nil")
	}

	// Should default to info level
	log.Info("test message")
}

func TestNewZapLogger_AllLevels(t *testing.T) {
	t.Parallel()

	levels := []string{"debug", "info", "warn", "error"}

	for _, level := range levels {
		t.Run(level, func(t *testing.T) {
			t.Parallel()

			log, err := NewZapLogger(level, "json")
			if err != nil {
				t.Fatalf("NewZapLogger(%q) failed: %v", level, err)
			}

			if log == nil {
				t.Fatalf("NewZapLogger(%q) returned nil", level)
			}

			// Test logging at each level
			log.Debug("debug message")
			log.Info("info message")
			log.Warn("warn message")
			log.Error("error message")
		})
	}
}

func TestZapLogger_With(t *testing.T) {
	t.Parallel()

	log, err := NewZapLogger("info", "json")
	if err != nil {
		t.Fatalf("NewZapLogger failed: %v", err)
	}

	childLog := log.With(
		logger.String("key", "value"),
		logger.Int("number", 42),
	)

	if childLog == nil {
		t.Fatal("With returned nil")
	}

	// Verify it's a different logger instance
	if childLog == log {
		t.Error("With should return a new logger instance")
	}

	// Test that child logger can log
	childLog.Info("test message with fields")
}

func TestZapLogger_WithContext_RequestID(t *testing.T) {
	t.Parallel()

	log, err := NewZapLogger("info", "json")
	if err != nil {
		t.Fatalf("NewZapLogger failed: %v", err)
	}

	ctx := context.WithValue(context.Background(), requestIDKey, "test-request-id")
	contextLog := log.WithContext(ctx)

	if contextLog == nil {
		t.Fatal("WithContext returned nil")
	}

	// Test that context logger can log
	contextLog.Info("test message with request ID")
}

func TestZapLogger_WithContext_UserID(t *testing.T) {
	t.Parallel()

	log, err := NewZapLogger("info", "json")
	if err != nil {
		t.Fatalf("NewZapLogger failed: %v", err)
	}

	ctx := context.WithValue(context.Background(), userIDKey, "test-user-id")
	contextLog := log.WithContext(ctx)

	if contextLog == nil {
		t.Fatal("WithContext returned nil")
	}

	// Test that context logger can log
	contextLog.Info("test message with user ID")
}

func TestZapLogger_WithContext_Both(t *testing.T) {
	t.Parallel()

	log, err := NewZapLogger("info", "json")
	if err != nil {
		t.Fatalf("NewZapLogger failed: %v", err)
	}

	ctx := context.WithValue(context.Background(), requestIDKey, "test-request-id")
	ctx = context.WithValue(ctx, userIDKey, "test-user-id")
	contextLog := log.WithContext(ctx)

	if contextLog == nil {
		t.Fatal("WithContext returned nil")
	}

	// Test that context logger can log
	contextLog.Info("test message with both IDs")
}

func TestZapLogger_WithContext_EmptyContext(t *testing.T) {
	t.Parallel()

	log, err := NewZapLogger("info", "json")
	if err != nil {
		t.Fatalf("NewZapLogger failed: %v", err)
	}

	ctx := context.Background()
	contextLog := log.WithContext(ctx)

	if contextLog == nil {
		t.Fatal("WithContext returned nil")
	}

	// With empty context, should return the same logger
	if contextLog != log {
		t.Error("WithContext with empty context should return the same logger")
	}
}

func TestZapLogger_LoggingMethods(t *testing.T) {
	t.Parallel()

	log, err := NewZapLogger("debug", "json")
	if err != nil {
		t.Fatalf("NewZapLogger failed: %v", err)
	}

	fields := []logger.Field{
		logger.String("key", "value"),
		logger.Int("number", 42),
		logger.Bool("flag", true),
	}

	// Test all logging methods
	log.Debug("debug message", fields...)
	log.Info("info message", fields...)
	log.Warn("warn message", fields...)
	log.Error("error message", fields...)
}

func TestConvertFields(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name   string
		fields []logger.Field
	}{
		{
			name:   "empty fields",
			fields: []logger.Field{},
		},
		{
			name: "valid zap fields",
			fields: []logger.Field{
				zap.String("key", "value"),
				zap.Int("number", 42),
			},
		},
		{
			name: "mixed fields",
			fields: []logger.Field{
				logger.String("key", "value"),
				zap.Int("number", 42),
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			log, err := NewZapLogger("info", "json")
			if err != nil {
				t.Fatalf("NewZapLogger failed: %v", err)
			}

			// Test that convertFields works by logging
			log.Info("test message", tt.fields...)
		})
	}
}

func TestRequestIDKey(t *testing.T) {
	t.Parallel()

	key := RequestIDKey()
	if key == "" {
		t.Error("RequestIDKey returned empty contextKey")
	}

	if key != requestIDKey {
		t.Errorf("RequestIDKey() = %v, want %v", key, requestIDKey)
	}
}

func TestUserIDKey(t *testing.T) {
	t.Parallel()

	key := UserIDKey()
	if key == "" {
		t.Error("UserIDKey returned empty contextKey")
	}

	if key != userIDKey {
		t.Errorf("UserIDKey() = %v, want %v", key, userIDKey)
	}
}

func TestZapLogger_ChainedWith(t *testing.T) {
	t.Parallel()

	log, err := NewZapLogger("info", "json")
	if err != nil {
		t.Fatalf("NewZapLogger failed: %v", err)
	}

	// Chain multiple With calls
	childLog := log.With(
		logger.String("parent", "value1"),
	).With(
		logger.String("child", "value2"),
	)

	if childLog == nil {
		t.Fatal("Chained With returned nil")
	}

	childLog.Info("test message with chained fields")
}

func TestZapLogger_WithContext_ChainedWith(t *testing.T) {
	t.Parallel()

	log, err := NewZapLogger("info", "json")
	if err != nil {
		t.Fatalf("NewZapLogger failed: %v", err)
	}

	ctx := context.WithValue(context.Background(), requestIDKey, "test-id")
	contextLog := log.WithContext(ctx).With(
		logger.String("additional", "field"),
	)

	if contextLog == nil {
		t.Fatal("Chained WithContext and With returned nil")
	}

	contextLog.Info("test message with context and additional fields")
}

// Benchmark tests
func BenchmarkZapLogger_Info(b *testing.B) {
	log, err := NewZapLogger("info", "json")
	if err != nil {
		b.Fatalf("NewZapLogger failed: %v", err)
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		log.Info("benchmark message")
	}
}

func BenchmarkZapLogger_InfoWithFields(b *testing.B) {
	log, err := NewZapLogger("info", "json")
	if err != nil {
		b.Fatalf("NewZapLogger failed: %v", err)
	}

	fields := []logger.Field{
		logger.String("key1", "value1"),
		logger.Int("key2", 42),
		logger.Bool("key3", true),
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		log.Info("benchmark message", fields...)
	}
}

func BenchmarkZapLogger_WithContext(b *testing.B) {
	log, err := NewZapLogger("info", "json")
	if err != nil {
		b.Fatalf("NewZapLogger failed: %v", err)
	}

	ctx := context.WithValue(context.Background(), requestIDKey, "test-id")

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = log.WithContext(ctx)
	}
}
