package logger

import (
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"testing"

	"github.com/everything-on-mysql/cassandra-on-mysql/internal/config"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"go.uber.org/zap/zaptest/observer"
)

func TestNewLogger(t *testing.T) {
	falseVal := false
	trueVal := true
	tests := []struct {
		name        string
		cfg         *config.LoggingConfig
		expectError bool
	}{
		{
			name: "production logger",
			cfg: &config.LoggingConfig{
				Level:          "info",
				Output:         "stdout",
				Development:    false,
				RedactQueries:  &falseVal,
				RedactPatterns: []string{},
			},
			expectError: false,
		},
		{
			name: "development logger",
			cfg: &config.LoggingConfig{
				Level:          "debug",
				Output:         "stdout",
				Development:    true,
				RedactQueries:  &falseVal,
				RedactPatterns: []string{},
			},
			expectError: false,
		},
		{
			name: "with redaction",
			cfg: &config.LoggingConfig{
				Level:          "info",
				Output:         "stdout",
				Development:    false,
				RedactQueries:  &trueVal,
				RedactPatterns: []string{},
			},
			expectError: false,
		},
		{
			name: "invalid log level defaults to info",
			cfg: &config.LoggingConfig{
				Level:          "invalid",
				Output:         "stdout",
				Development:    false,
				RedactQueries:  &falseVal,
				RedactPatterns: []string{},
			},
			expectError: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			logger, err := NewLogger(tt.cfg)
			if tt.expectError && err == nil {
				t.Error("Expected error but got none")
			}
			if !tt.expectError && err != nil {
				t.Errorf("Unexpected error: %v", err)
			}
			if logger != nil {
				// Verify logger is not nil
				if logger.Logger == nil {
					t.Error("Logger's embedded zap.Logger is nil")
				}
			}
		})
	}
}

func TestLoggerRedaction(t *testing.T) {
	// Create a test observer to capture log output
	core, recorded := observer.New(zapcore.DebugLevel)
	zapLogger := zap.New(core)

	redactor := NewRedactor(true, []string{})
	logger := newLogger(zapLogger, redactor)

	// Test logging with query field that should be redacted
	logger.Info("Executing query", zap.String("query", "SELECT * FROM users WHERE password='secret123'"))

	// Check the recorded log
	logs := recorded.All()
	if len(logs) != 1 {
		t.Fatalf("Expected 1 log entry, got %d", len(logs))
	}

	// The message should be redacted
	if logs[0].Message != "Executing query" {
		t.Errorf("Message not redacted correctly: got %q", logs[0].Message)
	}

	// Check if the query field was redacted
	queryField := logs[0].ContextMap()["query"]
	expectedQuery := "SELECT * FROM users WHERE password=[REDACTED]"
	if queryField != expectedQuery {
		t.Errorf("Query field not redacted correctly: got %q, want %q", queryField, expectedQuery)
	}
}

func TestLoggerRedactionDisabled(t *testing.T) {
	// Create a test observer to capture log output
	core, recorded := observer.New(zapcore.DebugLevel)
	zapLogger := zap.New(core)

	redactor := NewRedactor(false, []string{})
	logger := newLogger(zapLogger, redactor)

	// Test logging with query field - should NOT be redacted
	logger.Info("Executing query", zap.String("query", "SELECT * FROM users WHERE password='secret123'"))

	// Check the recorded log
	logs := recorded.All()
	if len(logs) != 1 {
		t.Fatalf("Expected 1 log entry, got %d", len(logs))
	}

	// The query field should NOT be redacted
	queryField := logs[0].ContextMap()["query"]
	if queryField != "SELECT * FROM users WHERE password='secret123'" {
		t.Errorf("Query field was redacted when it shouldn't be: got %q", queryField)
	}
}

func TestParseLogLevel(t *testing.T) {
	tests := []struct {
		input    string
		expected zapcore.Level
	}{
		{"debug", zapcore.DebugLevel},
		{"info", zapcore.InfoLevel},
		{"warn", zapcore.WarnLevel},
		{"warning", zapcore.WarnLevel},
		{"error", zapcore.ErrorLevel},
		{"fatal", zapcore.FatalLevel},
		{"", zapcore.InfoLevel},        // default
		{"unknown", zapcore.InfoLevel}, // default
	}

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

// --- Caller-line accuracy ---------------------------------------------------
//
// The caller annotation in logs must point at the real call site for BOTH usage
// patterns (GetZapLogger() used directly, and the wrapper methods) in BOTH
// development and production modes. A caller-skip mismatch silently corrupts
// every file:line in the logs and sends incident triage to the wrong code.

func newFileLogger(t *testing.T, development bool) (*Logger, func() string) {
	t.Helper()
	path := filepath.Join(t.TempDir(), "log.txt")
	falseVal := false
	lg, err := NewLogger(&config.LoggingConfig{
		Level:          "debug",
		Output:         "file",
		FilePath:       path,
		Development:    development,
		RedactQueries:  &falseVal,
		RedactPatterns: []string{},
	})
	if err != nil {
		t.Fatalf("NewLogger: %v", err)
	}
	read := func() string {
		_ = lg.GetZapLogger().Sync()
		b, err := os.ReadFile(path)
		if err != nil {
			t.Fatalf("read log file: %v", err)
		}
		return string(b)
	}
	return lg, read
}

var callerLineRe = regexp.MustCompile(`logger_test\.go:(\d+)`)

func callerLineFromLog(t *testing.T, content string) int {
	t.Helper()
	m := callerLineRe.FindStringSubmatch(content)
	if m == nil {
		t.Fatalf("no logger_test.go caller found in log output: %q", content)
	}
	n, err := strconv.Atoi(m[1])
	if err != nil {
		t.Fatalf("bad caller line %q: %v", m[1], err)
	}
	return n
}

func TestNewLoggerCallerAccurate_DevDirect(t *testing.T) {
	lg, read := newFileLogger(t, true)
	_, _, want, _ := runtime.Caller(0)
	lg.GetZapLogger().Error("dev-direct") // want+1
	if got := callerLineFromLog(t, read()); got != want+1 {
		t.Fatalf("dev GetZapLogger caller line = %d, want %d", got, want+1)
	}
}

func TestNewLoggerCallerAccurate_DevWrapper(t *testing.T) {
	lg, read := newFileLogger(t, true)
	_, _, want, _ := runtime.Caller(0)
	lg.Error("dev-wrapper") // want+1
	if got := callerLineFromLog(t, read()); got != want+1 {
		t.Fatalf("dev wrapper Error caller line = %d, want %d", got, want+1)
	}
}

func TestNewLoggerCallerAccurate_ProdDirect(t *testing.T) {
	lg, read := newFileLogger(t, false)
	_, _, want, _ := runtime.Caller(0)
	lg.GetZapLogger().Error("prod-direct") // want+1
	if got := callerLineFromLog(t, read()); got != want+1 {
		t.Fatalf("prod GetZapLogger caller line = %d, want %d", got, want+1)
	}
}

func TestNewLoggerCallerAccurate_ProdWrapper(t *testing.T) {
	lg, read := newFileLogger(t, false)
	_, _, want, _ := runtime.Caller(0)
	lg.Error("prod-wrapper") // want+1
	if got := callerLineFromLog(t, read()); got != want+1 {
		t.Fatalf("prod wrapper Error caller line = %d, want %d", got, want+1)
	}
}
