package astilog

import (
	"encoding/json"
	"fmt"
	"log"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/asticode/go-astikit"
)

type formatter interface {
	format(msg string, l astikit.LoggerLevel, fs map[string]interface{}) []byte
}

type textFormatter struct {
	c         Configuration
	createdAt time.Time
}

func newTextFormatter(c Configuration, createdAt time.Time) *textFormatter {
	return &textFormatter{
		c:         c,
		createdAt: createdAt,
	}
}

func (f *textFormatter) format(msg string, l astikit.LoggerLevel, fs map[string]interface{}) (b []byte) {
	// Add level
	switch l {
	case astikit.LoggerLevelDebug:
		b = append(b, []byte("DEBUG")...)
	case astikit.LoggerLevelWarn:
		b = append(b, []byte(" WARN")...)
	case astikit.LoggerLevelError:
		b = append(b, []byte("ERROR")...)
	case astikit.LoggerLevelFatal:
		b = append(b, []byte("FATAL")...)
	default:
		b = append(b, []byte(" INFO")...)
	}

	// Add timestamp
	b = append(b, []byte("[")...)
	if f.c.TimestampFormat == "" {
		b = append(b, astikit.BytesPad([]byte(strconv.Itoa(int(now().Sub(f.createdAt).Seconds()))), '0', 4)...)
	} else {
		b = append(b, []byte(now().Format(f.c.TimestampFormat))...)
	}
	b = append(b, []byte("]")...)

	// Add msg
	b = append(b, []byte(msg)...)

	// Add fields
	if len(fs) > 0 {
		// Add spaces
		b = append(b, []byte("  ")...)

		// Sort fields
		var vs []string
		for k, v := range fs {
			vs = append(vs, k+"="+fmt.Sprintf("%v", v))
		}
		sort.Strings(vs)
		b = append(b, []byte(strings.Join(vs, " "))...)
	}

	// Add newline
	b = append(b, newLine...)
	return
}

type jsonFormatter struct {
	c         Configuration
	createdAt time.Time
	msgKey    string
}

func newJSONFormatter(c Configuration, createdAt time.Time) (f *jsonFormatter) {
	f = &jsonFormatter{
		c:         c,
		createdAt: createdAt,
		msgKey:    "msg",
	}
	if c.MessageKey != "" {
		f.msgKey = c.MessageKey
	}
	return
}

func (f *jsonFormatter) format(msg string, l astikit.LoggerLevel, fs map[string]interface{}) []byte {
	// Add msg
	fs[f.msgKey] = msg

	// Add level
	fs["level"] = l

	// Add timestamp
	if f.c.TimestampFormat == "" {
		fs["time"] = int(now().Sub(f.createdAt).Seconds())
	} else {
		fs["time"] = now().Format(f.c.TimestampFormat)
	}

	// Marshal
	b, err := json.Marshal(fs)
	if err != nil {
		log.Println(fmt.Errorf("astilog: marshaling failed: %w", err))
		return nil
	}

	// Add newline
	b = append(b, newLine...)
	return b
}

type minimalistFormatter struct{}

func newMinimalistFormatter() *minimalistFormatter {
	return &minimalistFormatter{}
}

func (f *minimalistFormatter) format(msg string, l astikit.LoggerLevel, fs map[string]interface{}) []byte {
	return append([]byte(msg), newLine...)
}