package logging

// Custom logging hooks for powering our logs API.
// Modeled after https://github.com/sirupsen/logrus/blob/master/hooks/test/test.go

import (
	"math"
	"os"
	"path/filepath"
	"sync"
	"time"

	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
	"github.com/owncast/owncast/utils"
	"github.com/rifflock/lfshook"
	"github.com/sirupsen/logrus"
	logger "github.com/sirupsen/logrus"
)

const maxLogEntries = 500

// OCLogger represents the owncast internal logging.
type OCLogger struct {
	Entries  []logrus.Entry
	Warnings []logrus.Entry
	mu       sync.RWMutex
}

// Logger is the shared instance of the internal logger.
var Logger *OCLogger

// Setup configures our custom logging destinations.
func Setup(enableDebugOptions bool, enableVerboseLogging bool) {
	// Create the logging directory if needed
	loggingDirectory := filepath.Dir(getLogFilePath())
	if !utils.DoesFileExists(loggingDirectory) {
		if err := os.Mkdir(loggingDirectory, 0o700); err != nil {
			logger.Errorln("unable to create logs directory", loggingDirectory, err)
		}
	}

	// Write logs to a file
	path := getLogFilePath()
	writer, _ := rotatelogs.New(
		path+".%Y%m%d%H%M",
		rotatelogs.WithLinkName(path),
		rotatelogs.WithMaxAge(time.Duration(86400)*time.Second),
		rotatelogs.WithRotationTime(time.Duration(604800)*time.Second),
	)

	logMapping := lfshook.WriterMap{
		logrus.InfoLevel:  writer,
		logrus.DebugLevel: writer,
		logrus.TraceLevel: writer,
		logrus.WarnLevel:  writer,
		logrus.ErrorLevel: writer,
		logrus.FatalLevel: writer,
	}

	logger.AddHook(lfshook.NewHook(
		logMapping,
		&logger.TextFormatter{},
	))

	if enableVerboseLogging {
		logrus.SetLevel(logrus.TraceLevel)
	} else {
		logrus.SetLevel(logrus.InfoLevel)
	}

	// Write to stdout console
	logger.SetOutput(os.Stdout)

	// Write to our custom logging hook for the log API
	_logger := new(OCLogger)
	logger.AddHook(_logger)

	if enableDebugOptions {
		logrus.SetReportCaller(true)
	}

	Logger = _logger
}

// Fire runs for every logging request.
func (l *OCLogger) Fire(e *logger.Entry) error {
	// Store all log messages to return back in the logging API
	l.mu.Lock()
	defer l.mu.Unlock()

	// Append to log entries
	if len(l.Entries) > maxLogEntries {
		l.Entries = l.Entries[1:]
	}
	l.Entries = append(l.Entries, *e)

	if e.Level <= logger.WarnLevel {
		if len(l.Warnings) > maxLogEntries {
			l.Warnings = l.Warnings[1:]
		}
		l.Warnings = append(l.Warnings, *e)
	}

	return nil
}

// Levels specifies what log levels we care about.
func (l *OCLogger) Levels() []logrus.Level {
	return logrus.AllLevels
}

// AllEntries returns all entries that were logged.
func (l *OCLogger) AllEntries() []*logrus.Entry {
	l.mu.RLock()
	defer l.mu.RUnlock()

	// Make a copy so the returned value won't race with future log requests
	logCount := int(math.Min(float64(len(l.Entries)), maxLogEntries))
	entries := make([]*logrus.Entry, logCount)
	for i := 0; i < len(entries); i++ {
		// Make a copy, for safety
		entries[len(entries)-logCount:][i] = &l.Entries[i]
	}

	return entries
}

// WarningEntries returns all warning or greater that were logged.
func (l *OCLogger) WarningEntries() []*logrus.Entry {
	l.mu.RLock()
	defer l.mu.RUnlock()

	// Make a copy so the returned value won't race with future log requests
	logCount := int(math.Min(float64(len(l.Warnings)), maxLogEntries))
	entries := make([]*logrus.Entry, logCount)
	for i := 0; i < len(entries); i++ {
		// Make a copy, for safety
		entries[len(entries)-logCount:][i] = &l.Warnings[i]
	}

	return entries
}