package log

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"sync"

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

// Level represents log severity levels.
// Lower values are more verbose.
type Level int

const (
	// DebugLevel is for low-level tracing and structure inspection
	DebugLevel Level = iota
	// VerboseLevel is for detailed operational info (file listings, etc)
	VerboseLevel
	// InfoLevel is for operational summaries (default)
	InfoLevel
	// WarnLevel is for warnings
	WarnLevel
	// ErrorLevel is for errors
	ErrorLevel
	// FatalLevel is for fatal errors
	FatalLevel
)

func (l Level) String() string {
	switch l {
	case DebugLevel:
		return "debug"
	case VerboseLevel:
		return "verbose"
	case InfoLevel:
		return "info"
	case WarnLevel:
		return "warn"
	case ErrorLevel:
		return "error"
	case FatalLevel:
		return "fatal"
	default:
		return "unknown"
	}
}

var (
	// mu protects the output writers and level
	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
	// currentLevel is our log level (includes Verbose)
	currentLevel Level = InfoLevel
)

// 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.DebugLevel) // Let apex/log pass everything; we filter ourselves
}

// isEnabled returns true if messages at the given level should be logged.
func isEnabled(l Level) bool {
	mu.RLock()
	defer mu.RUnlock()
	return l >= currentLevel
}

// Fatalf logs a formatted message at fatal level.
func Fatalf(format string, args ...interface{}) {
	if isEnabled(FatalLevel) {
		log.Fatalf(format, args...)
	}
}

// Fatal logs a message at fatal level.
func Fatal(arg string) {
	if isEnabled(FatalLevel) {
		log.Fatal(arg)
	}
}

// Errorf logs a formatted message at error level.
func Errorf(format string, args ...interface{}) {
	if isEnabled(ErrorLevel) {
		log.Errorf(format, args...)
	}
}

// Error logs a message at error level.
func Error(arg string) {
	if isEnabled(ErrorLevel) {
		log.Error(arg)
	}
}

// Warnf logs a formatted message at warn level.
func Warnf(format string, args ...interface{}) {
	if isEnabled(WarnLevel) {
		log.Warnf(format, args...)
	}
}

// Warn logs a message at warn level.
func Warn(arg string) {
	if isEnabled(WarnLevel) {
		log.Warn(arg)
	}
}

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

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

// Verbosef logs a formatted message at verbose level.
func Verbosef(format string, args ...interface{}) {
	if isEnabled(VerboseLevel) {
		log.Infof(format, args...)
	}
}

// Verbose logs a message at verbose level.
func Verbose(arg string) {
	if isEnabled(VerboseLevel) {
		log.Info(arg)
	}
}

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

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

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

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

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

// VerbosityStepsToLogLevel converts a -v count to a log level.
// 0 returns InfoLevel, 1 returns VerboseLevel, 2+ returns DebugLevel.
func VerbosityStepsToLogLevel(l int) Level {
	switch l {
	case 0:
		return InfoLevel
	case 1:
		return VerboseLevel
	default:
		return 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(l Level) {
	mu.Lock()
	defer mu.Unlock()
	currentLevel = l
}

// GetLevel returns the current log level.
func GetLevel() Level {
	mu.RLock()
	defer mu.RUnlock()
	return currentLevel
}

// WithError returns a log entry with the error attached.
func WithError(e error) *log.Entry {
	return log.Log.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 clears the progress line when progress is complete.
func ProgressDone() {
	// Clear the line with spaces and return to beginning
	pterm.Print("\r\033[K")
}
