package log

import (
	"context"
	"encoding/json"
	"fmt"
	"log/slog"
	"math/rand"
	"os"
	"time"

	"git.sr.ht/~ashkeel/containers/sync"
	slogmulti "github.com/samber/slog-multi"
	"gopkg.in/natefinch/lumberjack.v2"
)

const (
	History       = 50
	Filename      = "strimertul.log"
	PanicFilename = "strimertul-panic.log"
)

var (
	LastLogs     = sync.NewSlice[Entry]()
	IncomingLogs = make(chan Entry, 100)
)

func Init(level slog.Level) {
	logStorage := NewLogStorage(level)
	fileLogger := &lumberjack.Logger{
		Filename:   Filename,
		MaxSize:    20,
		MaxBackups: 3,
		MaxAge:     28,
	}
	consoleHandler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
		Level: level,
	})
	fileHandler := slog.NewJSONHandler(fileLogger, &slog.HandlerOptions{
		AddSource: true,
		Level:     level,
	})
	logger := slog.New(slogmulti.Fanout(consoleHandler, fileHandler, logStorage))

	slog.SetDefault(logger)
}

type Entry struct {
	ID      string `json:"id"`
	Time    string `json:"time"`
	Level   string `json:"level"`
	Message string `json:"message"`
	Data    string `json:"data"`
}

type Storage struct {
	minLevel slog.Level
	attrs    []slog.Attr
	group    string
}

func (core *Storage) Enabled(_ context.Context, level slog.Level) bool {
	return level >= core.minLevel
}

func (core *Storage) Handle(_ context.Context, record slog.Record) error {
	attributes := flatAttributeMap(record)
	attrJSON, _ := json.Marshal(attributes)

	// Generate unique log ID
	id := fmt.Sprintf("%d-%d", time.Now().UnixNano(), rand.Int31())

	logEntry := Entry{
		ID:      id,
		Time:    record.Time.Format(time.RFC3339),
		Level:   record.Level.String(),
		Message: record.Message,
		Data:    string(attrJSON),
	}
	LastLogs.Push(logEntry)
	if LastLogs.Size() > History {
		LastLogs.Splice(0, 1)
	}
	IncomingLogs <- logEntry
	return nil
}

func flatAttributeMap(record slog.Record) map[string]any {
	attributes := map[string]any{}
	flatAttributes := func(attr slog.Attr) bool {
		type attrToParse struct {
			Prefix string
			Attr   slog.Attr
		}
		remaining := []attrToParse{{"", attr}}
		for len(remaining) > 0 {
			var current attrToParse
			current, remaining = remaining[0], remaining[1:]

			switch current.Attr.Value.Kind() {
			case slog.KindGroup:
				for _, subAttr := range current.Attr.Value.Group() {
					remaining = append(remaining, attrToParse{
						Prefix: current.Attr.Key + ".",
						Attr:   subAttr,
					})
				}
			default:
				attributes[current.Prefix+current.Attr.Key] = current.Attr.Value.Any()
			}
		}
		return true
	}
	record.Attrs(flatAttributes)
	return attributes
}

func (core *Storage) WithAttrs(attrs []slog.Attr) slog.Handler {
	return &Storage{
		minLevel: core.minLevel,
		attrs:    append(core.attrs, attrs...),
		group:    core.group,
	}
}

func (core *Storage) WithGroup(name string) slog.Handler {
	return &Storage{
		minLevel: core.minLevel,
		attrs:    core.attrs,
		group:    name,
	}
}

func NewLogStorage(level slog.Level) *Storage {
	return &Storage{
		minLevel: level,
	}
}

func ParseLogFields(data map[string]any) []any {
	fields := []any{}
	for k, v := range data {
		fields = append(fields, k, v)
	}
	return fields
}
