// Copyright 2023 The Hugo Authors. All rights reserved.
// Some functions in this file (see comments) is based on the Go source code,
// copyright The Go Authors and  governed by a BSD-style license.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package loggers

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

	"github.com/bep/logg"
	"github.com/bep/logg/handlers/multi"
	"github.com/gohugoio/hugo/common/terminal"
)

var (
	reservedFieldNamePrefix = "__h_field_"
	// FieldNameCmd is the name of the field that holds the command name.
	FieldNameCmd = reservedFieldNamePrefix + "_cmd"
	// Used to suppress statements.
	FieldNameStatementID = reservedFieldNamePrefix + "__h_field_statement_id"
)

// Options defines options for the logger.
type Options struct {
	Level               logg.Level
	Stdout              io.Writer
	Stderr              io.Writer
	Distinct            bool
	StoreErrors         bool
	HandlerPost         func(e *logg.Entry) error
	SuppresssStatements map[string]bool
}

// New creates a new logger with the given options.
func New(opts Options) Logger {
	if opts.Stdout == nil {
		opts.Stdout = os.Stdout
	}
	if opts.Stderr == nil {
		opts.Stderr = os.Stdout
	}
	if opts.Level == 0 {
		opts.Level = logg.LevelWarn
	}

	var logHandler logg.Handler
	if terminal.PrintANSIColors(os.Stdout) {
		logHandler = newDefaultHandler(opts.Stdout, opts.Stderr)
	} else {
		logHandler = newNoColoursHandler(opts.Stdout, opts.Stderr, false, nil)
	}

	errorsw := &strings.Builder{}
	logCounters := newLogLevelCounter()
	handlers := []logg.Handler{
		whiteSpaceTrimmer(),
		logHandler,
		logCounters,
	}

	if opts.HandlerPost != nil {
		var hookHandler logg.HandlerFunc = func(e *logg.Entry) error {
			opts.HandlerPost(e)
			return nil
		}
		handlers = append(handlers, hookHandler)
	}

	if opts.StoreErrors {
		h := newNoColoursHandler(io.Discard, errorsw, true, func(e *logg.Entry) bool {
			return e.Level >= logg.LevelError
		})

		handlers = append(handlers, h)
	}

	logHandler = multi.New(handlers...)

	var logOnce *logOnceHandler
	if opts.Distinct {
		logOnce = newLogOnceHandler(logg.LevelWarn)
		logHandler = newStopHandler(logOnce, logHandler)
	}

	if opts.SuppresssStatements != nil && len(opts.SuppresssStatements) > 0 {
		logHandler = newStopHandler(newSuppressStatementsHandler(opts.SuppresssStatements), logHandler)
	}

	logger := logg.New(
		logg.Options{
			Level:   opts.Level,
			Handler: logHandler,
		},
	)

	l := logger.WithLevel(opts.Level)

	reset := func() {
		logCounters.mu.Lock()
		defer logCounters.mu.Unlock()
		logCounters.counters = make(map[logg.Level]int)
		errorsw.Reset()
		if logOnce != nil {
			logOnce.reset()
		}
	}

	return &logAdapter{
		logCounters: logCounters,
		errors:      errorsw,
		reset:       reset,
		out:         opts.Stdout,
		level:       opts.Level,
		logger:      logger,
		debugl:      l.WithLevel(logg.LevelDebug),
		infol:       l.WithLevel(logg.LevelInfo),
		warnl:       l.WithLevel(logg.LevelWarn),
		errorl:      l.WithLevel(logg.LevelError),
	}
}

// NewDefault creates a new logger with the default options.
func NewDefault() Logger {
	opts := Options{
		Distinct: true,
		Level:    logg.LevelWarn,
		Stdout:   os.Stdout,
		Stderr:   os.Stdout,
	}
	return New(opts)
}

func LevelLoggerToWriter(l logg.LevelLogger) io.Writer {
	return logWriter{l: l}
}

type Logger interface {
	Debugf(format string, v ...any)
	Debugln(v ...any)
	Error() logg.LevelLogger
	Errorf(format string, v ...any)
	Errorln(v ...any)
	Errors() string
	Errorsf(id, format string, v ...any)
	Info() logg.LevelLogger
	InfoCommand(command string) logg.LevelLogger
	Infof(format string, v ...any)
	Infoln(v ...any)
	Level() logg.Level
	LoggCount(logg.Level) int
	Logger() logg.Logger
	Out() io.Writer
	Printf(format string, v ...any)
	Println(v ...any)
	PrintTimerIfDelayed(start time.Time, name string)
	Reset()
	Warn() logg.LevelLogger
	WarnCommand(command string) logg.LevelLogger
	Warnf(format string, v ...any)
	Warnln(v ...any)
}

type logAdapter struct {
	logCounters *logLevelCounter
	errors      *strings.Builder
	reset       func()
	out         io.Writer
	level       logg.Level
	logger      logg.Logger
	debugl      logg.LevelLogger
	infol       logg.LevelLogger
	warnl       logg.LevelLogger
	errorl      logg.LevelLogger
}

func (l *logAdapter) Debugf(format string, v ...any) {
	l.debugl.Logf(format, v...)
}

func (l *logAdapter) Debugln(v ...any) {
	l.debugl.Logf(l.sprint(v...))
}

func (l *logAdapter) Info() logg.LevelLogger {
	return l.infol
}

func (l *logAdapter) InfoCommand(command string) logg.LevelLogger {
	return l.infol.WithField(FieldNameCmd, command)
}

func (l *logAdapter) Infof(format string, v ...any) {
	l.infol.Logf(format, v...)
}

func (l *logAdapter) Infoln(v ...any) {
	l.infol.Logf(l.sprint(v...))
}

func (l *logAdapter) Level() logg.Level {
	return l.level
}

func (l *logAdapter) LoggCount(level logg.Level) int {
	l.logCounters.mu.RLock()
	defer l.logCounters.mu.RUnlock()
	return l.logCounters.counters[level]
}

func (l *logAdapter) Logger() logg.Logger {
	return l.logger
}

func (l *logAdapter) Out() io.Writer {
	return l.out
}

// PrintTimerIfDelayed prints a time statement to the FEEDBACK logger
// if considerable time is spent.
func (l *logAdapter) PrintTimerIfDelayed(start time.Time, name string) {
	elapsed := time.Since(start)
	milli := int(1000 * elapsed.Seconds())
	if milli < 500 {
		return
	}
	l.Printf("%s in %v ms", name, milli)
}

func (l *logAdapter) Printf(format string, v ...any) {
	// Add trailing newline if not present.
	if !strings.HasSuffix(format, "\n") {
		format += "\n"
	}
	fmt.Fprintf(l.out, format, v...)
}

func (l *logAdapter) Println(v ...any) {
	fmt.Fprintln(l.out, v...)
}

func (l *logAdapter) Reset() {
	l.reset()
}

func (l *logAdapter) Warn() logg.LevelLogger {
	return l.warnl
}

func (l *logAdapter) Warnf(format string, v ...any) {
	l.warnl.Logf(format, v...)
}

func (l *logAdapter) WarnCommand(command string) logg.LevelLogger {
	return l.warnl.WithField(FieldNameCmd, command)
}

func (l *logAdapter) Warnln(v ...any) {
	l.warnl.Logf(l.sprint(v...))
}

func (l *logAdapter) Error() logg.LevelLogger {
	return l.errorl
}

func (l *logAdapter) Errorf(format string, v ...any) {
	l.errorl.Logf(format, v...)
}

func (l *logAdapter) Errorln(v ...any) {
	l.errorl.Logf(l.sprint(v...))
}

func (l *logAdapter) Errors() string {
	return l.errors.String()
}

func (l *logAdapter) Errorsf(id, format string, v ...any) {
	l.errorl.WithField(FieldNameStatementID, id).Logf(format, v...)
}

func (l *logAdapter) sprint(v ...any) string {
	return strings.TrimRight(fmt.Sprintln(v...), "\n")
}

type logWriter struct {
	l logg.LevelLogger
}

func (w logWriter) Write(p []byte) (n int, err error) {
	w.l.Log(logg.String(string(p)))
	return len(p), nil
}
