package log

import (
	"fmt"
	"io"
	"os"
	"runtime"
	"sync"

	"github.com/apex/log"
	acli "github.com/apex/log/handlers/cli"
	"github.com/davecgh/go-spew/spew"
	"github.com/pterm/pterm"
)

// Level is an alias for apex/log.Level for use by callers without importing apex/log.
type Level = log.Level

var (
	// mu protects the output writers
	mu sync.RWMutex
	// stdout is the writer for progress output
	stdout io.Writer = os.Stdout
	// stderr is the writer for log output
	stderr io.Writer = os.Stderr
)

// SetOutput configures the output writers for the log package.
// stdout is used for progress output, stderr is used for log messages.
func SetOutput(out, err io.Writer) {
	mu.Lock()
	defer mu.Unlock()
	stdout = out
	stderr = err
	pterm.SetDefaultOutput(out)
}

// GetStdout returns the configured stdout writer.
func GetStdout() io.Writer {
	mu.RLock()
	defer mu.RUnlock()
	return stdout
}

// GetStderr returns the configured stderr writer.
func GetStderr() io.Writer {
	mu.RLock()
	defer mu.RUnlock()
	return stderr
}

// DisableStyling turns off colors and styling for terminal output.
func DisableStyling() {
	pterm.DisableColor()
	pterm.DisableStyling()
	pterm.Debug.Prefix.Text = ""
	pterm.Info.Prefix.Text = ""
	pterm.Success.Prefix.Text = ""
	pterm.Warning.Prefix.Text = ""
	pterm.Error.Prefix.Text = ""
	pterm.Fatal.Prefix.Text = ""
}

// Init initializes the logger with the CLI handler and default log level.
func Init() {
	mu.RLock()
	w := stderr
	mu.RUnlock()
	log.SetHandler(acli.New(w))
	log.SetLevel(log.InfoLevel)
}

// Infof logs a formatted message at info level.
func Infof(format string, args ...interface{}) {
	log.Infof(format, args...)
}

// Info logs a message at info level.
func Info(arg string) {
	log.Info(arg)
}

// Debugf logs a formatted message at debug level with caller location.
func Debugf(format string, args ...interface{}) {
	DebugReal(fmt.Sprintf(format, args...), 2)
}

// Debug logs a message at debug level with caller location.
func Debug(arg string) {
	DebugReal(arg, 2)
}

// DebugReal logs at debug level with caller info from the specified stack depth.
func DebugReal(arg string, cs int) {
	_, callerFile, callerLine, ok := runtime.Caller(cs)
	if !ok {
		return
	}
	tag := fmt.Sprintf("%s:%d: ", callerFile, callerLine)
	log.Debug(tag + arg)
}

// Dump logs a spew dump of the arguments at debug level.
func Dump(args ...interface{}) {
	DebugReal(spew.Sdump(args...), 2)
}

// EnableDebugLogging sets the log level to debug.
func EnableDebugLogging() {
	SetLevel(log.DebugLevel)
}

// VerbosityStepsToLogLevel converts a -v count to a log level.
// 0 returns InfoLevel, 1+ returns DebugLevel.
func VerbosityStepsToLogLevel(l int) log.Level {
	switch l {
	case 0:
		return log.InfoLevel
	case 1:
		return log.DebugLevel
	}
	// -vv or more
	return log.DebugLevel
}

// SetLevelFromVerbosity sets the log level based on -v flag count.
func SetLevelFromVerbosity(l int) {
	SetLevel(VerbosityStepsToLogLevel(l))
}

// SetLevel sets the global log level.
func SetLevel(arg log.Level) {
	log.SetLevel(arg)
}

// GetLogger returns the underlying apex/log Logger.
func GetLogger() *log.Logger {
	if logger, ok := log.Log.(*log.Logger); ok {
		return logger
	}
	panic("unable to get logger")
}

// GetLevel returns the current log level.
func GetLevel() log.Level {
	return GetLogger().Level
}

// WithError returns a log entry with the error attached.
func WithError(e error) *log.Entry {
	return GetLogger().WithError(e)
}

// Progressf prints a progress message that overwrites the current line.
// Use ProgressDone() when progress is complete to move to the next line.
func Progressf(format string, args ...interface{}) {
	pterm.Printf("\r"+format, args...)
}

// ProgressDone completes a progress line by printing a newline.
func ProgressDone() {
	pterm.Println()
}
