package tests

import (
	"context"
	"io"
	"log/slog"
	"os"
	"os/exec"
	"strings"
	"testing"

	"github.com/jfraeys/fetch_ml/internal/logging"
)

type recordingHandler struct {
	base []slog.Attr
	last []slog.Attr
}

func TestLoggerFatalExits(t *testing.T) {
	if os.Getenv("LOG_FATAL_TEST") == "1" {
		logger := logging.NewLogger(slog.LevelInfo, false)
		logger.Fatal("fatal message")
		return
	}

	//nolint:gosec // G204: Subprocess launched with potential tainted input - this is a test
	cmd := exec.CommandContext(context.Background(), os.Args[0], "-test.run", t.Name())
	cmd.Env = append(os.Environ(), "LOG_FATAL_TEST=1")
	if err := cmd.Run(); err == nil {
		t.Fatalf("expected Fatal to exit with non-nil error")
	}
}

func TestNewLoggerHonorsJSONFormatEnv(t *testing.T) {
	t.Setenv("LOG_FORMAT", "json")
	origStderr := os.Stderr
	r, w, err := os.Pipe()
	if err != nil {
		t.Fatalf("failed to create pipe: %v", err)
	}
	os.Stderr = w
	defer func() {
		_ = w.Close()
		_ = r.Close()
		os.Stderr = origStderr
	}()

	logger := logging.NewLogger(slog.LevelInfo, false)
	logger.Info("hello", "key", "value")
	_ = w.Close()
	data, readErr := io.ReadAll(r)
	if readErr != nil {
		t.Fatalf("failed to read logger output: %v", readErr)
	}

	output := string(data)
	if !strings.Contains(output, "\"msg\":\"hello\"") || !strings.Contains(output, "\"key\":\"value\"") {
		t.Fatalf("expected json output, got %s", output)
	}
}

func (h *recordingHandler) Enabled(_ context.Context, _ slog.Level) bool {
	return true
}

func (h *recordingHandler) Handle(_ context.Context, r slog.Record) error {
	// Reset last and include base attributes first
	h.last = append([]slog.Attr{}, h.base...)
	r.Attrs(func(a slog.Attr) bool {
		h.last = append(h.last, a)
		return true
	})
	return nil
}

func (h *recordingHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
	newBase := append([]slog.Attr{}, h.base...)
	newBase = append(newBase, attrs...)
	return &recordingHandler{base: newBase}
}

func (h *recordingHandler) WithGroup(_ string) slog.Handler {
	return h
}

func attrsToMap(attrs []slog.Attr) map[string]any {
	out := make(map[string]any, len(attrs))
	for _, attr := range attrs {
		out[attr.Key] = attr.Value.Any()
	}
	return out
}

func TestEnsureTraceAddsIDs(t *testing.T) {
	ctx := context.Background()
	ctx = logging.EnsureTrace(ctx)

	if ctx.Value(logging.CtxTraceID) == nil {
		t.Fatalf("expected trace id to be injected")
	}
	if ctx.Value(logging.CtxSpanID) == nil {
		t.Fatalf("expected span id to be injected")
	}

	existingTrace := ctx.Value(logging.CtxTraceID)
	ctx = logging.EnsureTrace(ctx)
	if ctx.Value(logging.CtxTraceID) != existingTrace {
		t.Fatalf("EnsureTrace should not overwrite existing trace id")
	}
}

func TestLoggerWithContextIncludesValues(t *testing.T) {
	handler := &recordingHandler{}
	base := slog.New(handler)
	logger := &logging.Logger{Logger: base}

	ctx := context.Background()
	ctx = context.WithValue(ctx, logging.CtxTraceID, "trace-123")
	ctx = context.WithValue(ctx, logging.CtxSpanID, "span-456")
	ctx = logging.CtxWithWorker(ctx, "worker-1")
	ctx = logging.CtxWithJob(ctx, "job-a")
	ctx = logging.CtxWithTask(ctx, "task-b")

	child := logger.WithContext(ctx)
	child.Info("hello")

	rec, ok := child.Handler().(*recordingHandler)
	if !ok {
		t.Fatalf("expected recordingHandler, got %T", child.Handler())
	}

	fields := attrsToMap(rec.last)
	expected := map[string]string{
		"trace_id":  "trace-123",
		"span_id":   "span-456",
		"worker_id": "worker-1",
		"job_name":  "job-a",
		"task_id":   "task-b",
	}

	for key, want := range expected {
		got, ok := fields[key]
		if !ok {
			t.Fatalf("expected attribute %s to be present", key)
		}
		if got != want {
			t.Fatalf("attribute %s mismatch: want %s got %v", key, want, got)
		}
	}
}

func TestColorTextHandlerAddsColorAttr(t *testing.T) {
	tmp, err := os.CreateTemp("", "log-output")
	if err != nil {
		t.Fatalf("failed to create temp file: %v", err)
	}
	t.Cleanup(func() {
		defer func() { _ = tmp.Close() }()
		defer func() { _ = os.Remove(tmp.Name()) }()
	})

	handler := logging.NewColorTextHandler(tmp, &slog.HandlerOptions{Level: slog.LevelInfo})
	logger := slog.New(handler)

	logger.Info("color test")

	if err := tmp.Sync(); err != nil {
		t.Fatalf("failed to sync temp file: %v", err)
	}

	data, err := os.ReadFile(tmp.Name())
	if err != nil {
		t.Fatalf("failed to read temp file: %v", err)
	}

	output := string(data)
	if !strings.Contains(output, "lvl_color=\"\x1b[32mINF\x1b[0m\"") &&
		!strings.Contains(output, "lvl_color=\"\\x1b[32mINF\\x1b[0m\"") {
		t.Fatalf("expected info level color attribute, got: %s", output)
	}
}
