package logging

import (
	"context"
	"encoding/json"
	"io"
	"log/slog"
	"os"
	"path/filepath"
	"sync"
	"time"
)

type Closer func() error

type jsonLineHandler struct {
	mu    *sync.Mutex
	out   io.Writer
	attrs []slog.Attr
}

func New(dataDir string) (*slog.Logger, Closer, error) {
	logDir := filepath.Join(dataDir, "logs")
	if err := os.MkdirAll(logDir, 0o755); err != nil {
		return nil, nil, err
	}

	path := filepath.Join(logDir, time.Now().Format("2006-01-02")+".log")
	file, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
	if err != nil {
		return nil, nil, err
	}

	handler := &jsonLineHandler{
		mu:  &sync.Mutex{},
		out: io.MultiWriter(os.Stdout, file),
	}

	return slog.New(handler), file.Close, nil
}

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

func (h *jsonLineHandler) Handle(_ context.Context, record slog.Record) error {
	entry := map[string]any{
		"date":     record.Time.Format("2006-01-02"),
		"time":     record.Time.Format("15:04:05"),
		"source":   "app",
		"code":     0,
		"severity": severity(record.Level),
		"log":      record.Message,
	}

	for _, attr := range h.attrs {
		applyAttr(entry, attr)
	}
	record.Attrs(func(attr slog.Attr) bool {
		applyAttr(entry, attr)
		return true
	})

	line, err := json.Marshal(entry)
	if err != nil {
		return err
	}

	h.mu.Lock()
	defer h.mu.Unlock()
	_, err = h.out.Write(append(line, '\n'))
	return err
}

func (h *jsonLineHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
	next := &jsonLineHandler{
		mu:    h.mu,
		out:   h.out,
		attrs: append([]slog.Attr{}, h.attrs...),
	}
	next.attrs = append(next.attrs, attrs...)
	return next
}

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

func applyAttr(entry map[string]any, attr slog.Attr) {
	attr.Value = attr.Value.Resolve()
	entry[attr.Key] = attr.Value.Any()
}

func severity(level slog.Level) string {
	switch {
	case level >= slog.LevelError:
		return "error"
	case level >= slog.LevelWarn:
		return "warn"
	case level <= slog.LevelDebug:
		return "dev"
	default:
		return "dev"
	}
}
