package handler

import (
	"context"
	"log/slog"
	"sync"
	"time"
)

// LogEntry is one captured slog record, in a shape suitable for JSON.
type LogEntry struct {
	Time    time.Time      `json:"time"`
	Level   string         `json:"level"`
	Message string         `json:"message"`
	Attrs   map[string]any `json:"attrs,omitempty"`
}

// LogRing is a fixed-size circular buffer of recent slog records, populated
// by RingHandler. Snapshot returns a copy in chronological order (oldest →
// newest); the buffer is never blocking.
type LogRing struct {
	mu      sync.Mutex
	entries []LogEntry
	size    int
	next    int // index of the slot the next record will be written into
	count   int // number of entries currently held (≤ size)
}

// NewLogRing creates a ring with capacity n. Panics on n ≤ 0 — that's a
// programming error, not a runtime condition.
func NewLogRing(n int) *LogRing {
	if n <= 0 {
		panic("logring: capacity must be > 0")
	}
	return &LogRing{
		entries: make([]LogEntry, n),
		size:    n,
	}
}

func (r *LogRing) push(e LogEntry) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.entries[r.next] = e
	r.next = (r.next + 1) % r.size
	if r.count < r.size {
		r.count++
	}
}

// Snapshot returns the current contents in chronological order.
func (r *LogRing) Snapshot() []LogEntry {
	r.mu.Lock()
	defer r.mu.Unlock()
	out := make([]LogEntry, r.count)
	if r.count < r.size {
		copy(out, r.entries[:r.count])
		return out
	}
	// Buffer full and wrapping: oldest is at r.next, newest at r.next-1.
	copy(out, r.entries[r.next:])
	copy(out[r.size-r.next:], r.entries[:r.next])
	return out
}

// RingHandler is a slog.Handler that pushes records into a LogRing.
type RingHandler struct {
	ring  *LogRing
	level slog.Leveler
	attrs []slog.Attr
	group string
}

// NewRingHandler returns a handler that records into ring at level and above.
func NewRingHandler(ring *LogRing, level slog.Leveler) *RingHandler {
	return &RingHandler{ring: ring, level: level}
}

func (h *RingHandler) Enabled(_ context.Context, l slog.Level) bool {
	min := slog.LevelInfo
	if h.level != nil {
		min = h.level.Level()
	}
	return l >= min
}

func (h *RingHandler) Handle(_ context.Context, r slog.Record) error {
	attrs := make(map[string]any, r.NumAttrs()+len(h.attrs))
	for _, a := range h.attrs {
		attrs[a.Key] = a.Value.Any()
	}
	r.Attrs(func(a slog.Attr) bool {
		attrs[a.Key] = a.Value.Any()
		return true
	})
	if len(attrs) == 0 {
		attrs = nil
	}
	h.ring.push(LogEntry{
		Time:    r.Time,
		Level:   r.Level.String(),
		Message: r.Message,
		Attrs:   attrs,
	})
	return nil
}

func (h *RingHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
	merged := make([]slog.Attr, 0, len(h.attrs)+len(attrs))
	merged = append(merged, h.attrs...)
	merged = append(merged, attrs...)
	return &RingHandler{ring: h.ring, level: h.level, attrs: merged, group: h.group}
}

func (h *RingHandler) WithGroup(name string) slog.Handler {
	// We don't render groups specially — just track the deepest one for
	// debugging context. Most zddc-server logging is flat.
	return &RingHandler{ring: h.ring, level: h.level, attrs: h.attrs, group: name}
}

// MultiHandler fans out each Handle call to every wrapped handler. Used to
// tee slog output to both stderr (the existing TextHandler) and the in-memory
// ring buffer that backs the /.profile/logs endpoint.
type MultiHandler struct {
	handlers []slog.Handler
}

// NewMultiHandler returns a handler that broadcasts to all of hs.
func NewMultiHandler(hs ...slog.Handler) *MultiHandler {
	return &MultiHandler{handlers: hs}
}

func (m *MultiHandler) Enabled(ctx context.Context, l slog.Level) bool {
	for _, h := range m.handlers {
		if h.Enabled(ctx, l) {
			return true
		}
	}
	return false
}

func (m *MultiHandler) Handle(ctx context.Context, r slog.Record) error {
	var firstErr error
	for _, h := range m.handlers {
		if !h.Enabled(ctx, r.Level) {
			continue
		}
		if err := h.Handle(ctx, r.Clone()); err != nil && firstErr == nil {
			firstErr = err
		}
	}
	return firstErr
}

func (m *MultiHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
	out := make([]slog.Handler, len(m.handlers))
	for i, h := range m.handlers {
		out[i] = h.WithAttrs(attrs)
	}
	return &MultiHandler{handlers: out}
}

func (m *MultiHandler) WithGroup(name string) slog.Handler {
	out := make([]slog.Handler, len(m.handlers))
	for i, h := range m.handlers {
		out[i] = h.WithGroup(name)
	}
	return &MultiHandler{handlers: out}
}
