package logger

import (
	"fmt"
	"io"
	"strings"
	"time"

	"gitlab.com/uafrica/go-utils/errors"
)

type Logger interface {
	Fatalf(format string, args ...interface{})
	Fatal(args ...interface{})
	Errorf(format string, args ...interface{})
	Error(args ...interface{})
	Warnf(format string, args ...interface{})
	Warn(args ...interface{})
	Infof(format string, args ...interface{})
	Info(args ...interface{})
	Debugf(format string, args ...interface{})
	Debug(args ...interface{})
	Tracef(format string, args ...interface{})
	Trace(args ...interface{})

	WithFields(data map[string]interface{}) logger
}

type logger struct {
	level  Level
	writer io.Writer
	data   map[string]interface{}
	IFormatter
}

func (l logger) WithFields(data map[string]interface{}) logger {
	newLogger := logger{
		level:      l.level,
		writer:     l.writer,
		data:       map[string]interface{}{},
		IFormatter: l.IFormatter,
	}
	for n, v := range l.data {
		newLogger.data[n] = v
	}
	for n, v := range data {
		newLogger.data[n] = v
	}
	return newLogger
}

func (l logger) Fatalf(format string, args ...interface{}) {
	l.WithFields(map[string]interface{}{"call_stack": errors.Stack(3)}).log(LevelFatal, 1, fmt.Sprintf(format, args...))
}

func (l logger) Fatal(args ...interface{}) {
	l.WithFields(map[string]interface{}{"call_stack": errors.Stack(3)}).log(LevelFatal, 1, fmt.Sprint(args...))
}

func (l logger) Errorf(format string, args ...interface{}) {
	l.log(LevelError, 1, fmt.Sprintf(format, args...))
}

func (l logger) Error(args ...interface{}) {
	l.log(LevelError, 1, fmt.Sprint(args...))
}

func (l logger) Warnf(format string, args ...interface{}) {
	l.log(LevelWarn, 1, fmt.Sprintf(format, args...))
}

func (l logger) Warn(args ...interface{}) {
	l.log(LevelWarn, 1, fmt.Sprint(args...))
}

func (l logger) Infof(format string, args ...interface{}) {
	l.log(LevelInfo, 1, fmt.Sprintf(format, args...))
}

func (l logger) Info(args ...interface{}) {
	l.log(LevelInfo, 1, fmt.Sprint(args...))
}

func (l logger) Debugf(format string, args ...interface{}) {
	l.log(LevelDebug, 1, fmt.Sprintf(format, args...))
}

func (l logger) Debug(args ...interface{}) {
	l.log(LevelDebug, 1, fmt.Sprint(args...))
}

func (l logger) Tracef(format string, args ...interface{}) {
	l.log(LevelTrace, 1, fmt.Sprintf(format, args...))
}

func (l logger) Trace(args ...interface{}) {
	l.log(LevelTrace, 1, fmt.Sprint(args...))
}

func (l logger) log(level Level, skip int, msg string) {
	if level <= l.level && l.writer != nil {
		entry := Entry{
			Timestamp: time.Now(),
			Level:     level,
			Caller:    errors.GetCaller(skip + 2).Info(),
			Data:      l.data,
			Message:   strings.ReplaceAll(msg, "\n", ";"),
		}
		l.writer.Write(l.Format(entry))
	}
}

type Entry struct {
	Timestamp time.Time              `json:"time"`
	Level     Level                  `json:"level"`
	Caller    errors.CallerInfo      `json:"caller"`
	Data      map[string]interface{} `json:"data"`
	Message   string                 `json:"message"`
}