package logging

import (
	"fmt"
	"io"
	"sync"
	"time"
)

type Logger interface {
	// Error logs an error message.
	Error(msg ...any)
	// Errorf logs a formatted error message.
	Errorf(format string, args ...any)
	// Warn logs a warning message.
	Warn(msg ...any)
	// Warnf logs a formatted warning message.
	Warnf(format string, args ...any)
	// Info logs an info message.
	Info(msg ...any)
	// Infof logs a formatted info message.
	Infof(format string, args ...any)
	// Log prints a line to the output writer with a header.
	Log(msg ...any)
	// Logf prints a formatted line to the output writer with a header.
	Logf(format string, args ...any)

	// Verbose returns the logger instance if verbose logging is enabled, and otherwise returns nil.
	// A nil logger created with `logging.NewLogger` is safe to call methods on.
	Verbose() Logger
	// IsVerbose returns true if verbose logging is enabled, and false otherwise.
	IsVerbose() bool
	// SetVerbose sets the verbose logging flag.
	SetVerbose(verbose bool)
}

var _ Logger = (*logger)(nil)

type logger struct {
	mu      sync.Mutex
	verbose bool
	writer  io.Writer
	prefix  func() string
}

func (l *logger) Log(msg ...any) {
	if l == nil {
		return
	}
	l.mu.Lock()
	defer l.mu.Unlock()
	fmt.Fprintln(l.writer, l.prefix(), fmt.Sprint(msg...))
}

func (l *logger) Logf(format string, args ...any) {
	if l == nil {
		return
	}
	l.mu.Lock()
	defer l.mu.Unlock()
	fmt.Fprintf(l.writer, "%s %s\n", l.prefix(), fmt.Sprintf(format, args...))
}

func (l *logger) Verbose() Logger {
	if l == nil {
		return nil
	}
	l.mu.Lock()
	defer l.mu.Unlock()
	if !l.verbose {
		return nil
	}
	return l
}

func (l *logger) IsVerbose() bool {
	if l == nil {
		return false
	}
	l.mu.Lock()
	defer l.mu.Unlock()
	return l.verbose
}

func (l *logger) SetVerbose(verbose bool) {
	if l == nil {
		return
	}
	l.mu.Lock()
	defer l.mu.Unlock()
	l.verbose = verbose
}

func (l *logger) Error(msg ...any) {
	l.Log(msg...)
}

func (l *logger) Errorf(format string, args ...any) {
	l.Logf(format, args...)
}

func (l *logger) Warn(msg ...any) {
	l.Log(msg...)
}

func (l *logger) Warnf(format string, args ...any) {
	l.Logf(format, args...)
}

func (l *logger) Info(msg ...any) {
	l.Log(msg...)
}

func (l *logger) Infof(format string, args ...any) {
	l.Logf(format, args...)
}

func NewLogger(output io.Writer) Logger {
	return &logger{
		writer: output,
		prefix: func() string {
			return formatTime(time.Now())
		},
	}
}

// NewNopLogger returns a no-op Logger that discards all log messages.
// It is safe to call any method on the returned Logger.
func NewNopLogger() Logger {
	return (*logger)(nil)
}

func formatTime(t time.Time) string {
	return fmt.Sprintf("[%s]", t.Format("15:04:05.000"))
}
