package logging

import (
	"context"
	"fmt"
	"io"
	"log/slog"
	"os"
	"strings"
	"sync"
	"time"
)

type Entry struct {
	Timestamp time.Time      `json:"timestamp"`
	Level     string         `json:"level"`
	Message   string         `json:"message"`
	Fields    map[string]any `json:"fields,omitempty"`
}

type RecentStore struct {
	mu       sync.RWMutex
	entries  []Entry
	next     int
	size     int
	capacity int
}

func NewRecentStore(capacity int) *RecentStore {
	if capacity <= 0 {
		capacity = 200
	}
	return &RecentStore{
		entries:  make([]Entry, capacity),
		capacity: capacity,
	}
}

func (s *RecentStore) Add(entry Entry) {
	if s == nil {
		return
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	s.entries[s.next] = entry
	s.next = (s.next + 1) % s.capacity
	if s.size < s.capacity {
		s.size++
	}
}

func (s *RecentStore) List(limit int) []Entry {
	if s == nil {
		return nil
	}
	s.mu.RLock()
	defer s.mu.RUnlock()
	if s.size == 0 {
		return nil
	}
	if limit <= 0 || limit > s.size {
		limit = s.size
	}
	out := make([]Entry, 0, limit)
	for i := 0; i < limit; i++ {
		idx := (s.next - 1 - i + s.capacity) % s.capacity
		out = append(out, s.entries[idx])
	}
	return out
}

type recentHandler struct {
	store *RecentStore
	level slog.Leveler
	attrs []slog.Attr
	group []string
}

func NewRecentHandler(store *RecentStore, level slog.Leveler) slog.Handler {
	if level == nil {
		level = slog.LevelInfo
	}
	return &recentHandler{store: store, level: level}
}

func (h *recentHandler) Enabled(_ context.Context, level slog.Level) bool {
	if h == nil || h.level == nil {
		return level >= slog.LevelInfo
	}
	return level >= h.level.Level()
}

func (h *recentHandler) Handle(_ context.Context, r slog.Record) error {
	if h == nil || h.store == nil {
		return nil
	}
	fields := map[string]any{}
	for _, attr := range h.attrs {
		addField(fields, h.group, attr)
	}
	r.Attrs(func(attr slog.Attr) bool {
		addField(fields, h.group, attr)
		return true
	})
	if len(fields) == 0 {
		fields = nil
	}
	h.store.Add(Entry{
		Timestamp: r.Time.UTC(),
		Level:     r.Level.String(),
		Message:   strings.TrimSpace(r.Message),
		Fields:    fields,
	})
	return nil
}

func (h *recentHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
	next := &recentHandler{
		store: h.store,
		level: h.level,
		group: append([]string(nil), h.group...),
	}
	next.attrs = append(append([]slog.Attr(nil), h.attrs...), attrs...)
	return next
}

func (h *recentHandler) WithGroup(name string) slog.Handler {
	next := &recentHandler{
		store: h.store,
		level: h.level,
		attrs: append([]slog.Attr(nil), h.attrs...),
		group: append([]string(nil), h.group...),
	}
	if trimmed := strings.TrimSpace(name); trimmed != "" {
		next.group = append(next.group, trimmed)
	}
	return next
}

func addField(fields map[string]any, groups []string, attr slog.Attr) {
	if attr.Equal(slog.Attr{}) {
		return
	}
	key := strings.TrimSpace(attr.Key)
	if key == "" {
		return
	}
	prefix := strings.Join(groups, ".")
	if prefix != "" {
		key = prefix + "." + key
	}
	val := attr.Value.Resolve()
	if val.Kind() == slog.KindGroup {
		for _, nested := range val.Group() {
			addField(fields, append(groups, strings.TrimSpace(attr.Key)), nested)
		}
		return
	}
	fields[key] = slogValueToAny(val)
}

func slogValueToAny(v slog.Value) any {
	switch v.Kind() {
	case slog.KindString:
		return v.String()
	case slog.KindBool:
		return v.Bool()
	case slog.KindInt64:
		return v.Int64()
	case slog.KindUint64:
		return v.Uint64()
	case slog.KindFloat64:
		return v.Float64()
	case slog.KindDuration:
		return v.Duration().Milliseconds()
	case slog.KindTime:
		return v.Time().UTC().Format(time.RFC3339Nano)
	case slog.KindAny:
		return v.Any()
	default:
		return fmt.Sprint(v.Any())
	}
}

type teeHandler struct {
	handlers []slog.Handler
}

func newTeeHandler(handlers ...slog.Handler) slog.Handler {
	nonNil := make([]slog.Handler, 0, len(handlers))
	for _, h := range handlers {
		if h != nil {
			nonNil = append(nonNil, h)
		}
	}
	return &teeHandler{handlers: nonNil}
}

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

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

func (h *teeHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
	next := make([]slog.Handler, 0, len(h.handlers))
	for _, handler := range h.handlers {
		next = append(next, handler.WithAttrs(attrs))
	}
	return &teeHandler{handlers: next}
}

func (h *teeHandler) WithGroup(name string) slog.Handler {
	next := make([]slog.Handler, 0, len(h.handlers))
	for _, handler := range h.handlers {
		next = append(next, handler.WithGroup(name))
	}
	return &teeHandler{handlers: next}
}

type SetupResult struct {
	Logger *slog.Logger
	Recent *RecentStore
	Close  func() error
}

func Setup(levelRaw string) SetupResult {
	level := parseLevel(levelRaw)
	handlerOptions := &slog.HandlerOptions{Level: level}
	recent := NewRecentStore(400)
	stdoutHandler := slog.NewJSONHandler(os.Stdout, handlerOptions)
	handlers := []slog.Handler{stdoutHandler, NewRecentHandler(recent, level)}

	closers := make([]io.Closer, 0, 1)
	if path := strings.TrimSpace(os.Getenv("LOG_FILE")); path != "" {
		if file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644); err == nil {
			handlers = append(handlers, slog.NewJSONHandler(file, handlerOptions))
			closers = append(closers, file)
		}
	}

	logger := slog.New(newTeeHandler(handlers...))
	return SetupResult{
		Logger: logger,
		Recent: recent,
		Close: func() error {
			for _, closer := range closers {
				_ = closer.Close()
			}
			return nil
		},
	}
}

func parseLevel(raw string) slog.Level {
	switch strings.ToLower(strings.TrimSpace(raw)) {
	case "trace":
		return slog.LevelDebug - 4
	case "debug":
		return slog.LevelDebug
	case "warn", "warning":
		return slog.LevelWarn
	case "error":
		return slog.LevelError
	default:
		return slog.LevelInfo
	}
}
