package logging

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

func TestNewLogger(t *testing.T) {
	cfg := DefaultConfig()
	cfg.RedactEnabled = false // Simplify for this test
	logger := New(cfg)

	if logger == nil {
		t.Fatal("expected non-nil logger")
	}
	if logger.Slog() == nil {
		t.Fatal("expected non-nil slog.Logger")
	}
}

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

	// Test With method
	logger2 := logger.With("key", "value")
	logger2.Info("test message")

	output := buf.String()
	if !strings.Contains(output, `"key"`) || !strings.Contains(output, `"value"`) {
		t.Errorf("expected key/value in output, got: %s", output)
	}
}

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

	logger.WithComponent("test_component").Info("test message")

	output := buf.String()
	if !strings.Contains(output, FieldComponent) || !strings.Contains(output, "test_component") {
		t.Errorf("expected component field in output, got: %s", output)
	}
}

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

	logger.WithHandler("CreateProject").Info("test message")

	output := buf.String()
	if !strings.Contains(output, FieldHandler) || !strings.Contains(output, "CreateProject") {
		t.Errorf("expected handler field in output, got: %s", output)
	}
}

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

	err := &testError{msg: "something went wrong"}
	logger.WithError(err).Error("operation failed")

	output := buf.String()
	// Verify we use "error" field, not "err" or "e"
	if !strings.Contains(output, `"error"`) || !strings.Contains(output, "something went wrong") {
		t.Errorf("expected error field in output, got: %s", output)
	}
}

type testError struct {
	msg string
}

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

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

	// WithError(nil) should not add error field
	logger2 := logger.WithError(nil)
	if logger2 != logger {
		t.Error("WithError(nil) should return same logger")
	}
}

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

	// Test Timed method
	done := logger.Timed("test_operation")
	time.Sleep(10 * time.Millisecond)
	done()

	output := buf.String()
	if !strings.Contains(output, "test_operation") {
		t.Errorf("expected operation in output, got: %s", output)
	}
	if !strings.Contains(output, FieldDuration) {
		t.Errorf("expected duration field in output, got: %s", output)
	}
	if !strings.Contains(output, "operation completed") {
		t.Errorf("expected completion message in output, got: %s", output)
	}
}

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

	logger.Info("test message", "key", "value")

	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())
	}
	if entry["msg"] != "test message" {
		t.Errorf("expected msg='test message', got: %v", entry["msg"])
	}
	if entry["key"] != "value" {
		t.Errorf("expected key='value', got: %v", entry["key"])
	}
}

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

	logger.Info("test message", "key", "value")

	output := buf.String()
	// Text format should contain the message and key=value
	if !strings.Contains(output, "test message") {
		t.Errorf("expected message in output, got: %s", output)
	}
	if !strings.Contains(output, "key=value") {
		t.Errorf("expected key=value in output, got: %s", output)
	}
}

func TestFromContext(t *testing.T) {
	cfg := DefaultConfig()
	cfg.RedactEnabled = false
	logger := New(cfg)
	SetDefault(logger)

	// No logger in context should return default
	ctx := context.Background()
	l := FromContext(ctx)
	if l == nil {
		t.Fatal("expected non-nil logger from empty context")
	}

	// Logger in context should be returned
	enriched := logger.With("test", "value")
	ctx = WithContext(ctx, enriched)
	l = FromContext(ctx)
	if l != enriched {
		t.Error("expected enriched logger from context")
	}
}

func TestFromContextNil(t *testing.T) {
	cfg := DefaultConfig()
	cfg.RedactEnabled = false
	logger := New(cfg)
	SetDefault(logger)

	// Test that FromContext handles nil context gracefully
	// This is intentionally testing nil context handling
	var nilCtx context.Context //nolint:staticcheck // testing nil context handling
	l := FromContext(nilCtx)
	if l == nil {
		t.Fatal("expected non-nil logger from nil context")
	}
}

func TestNop(t *testing.T) {
	logger := Nop()
	if logger == nil {
		t.Fatal("expected non-nil nop logger")
	}

	// Should not panic when called
	logger.Info("this should be discarded")
	logger.WithComponent("test").Error("this too")
}
