// Package logger provides a structured logger with source location tracking.
// It wraps the standard library's log/slog package and automatically enriches
// log messages with the file name, line number, and function name of the caller.
// The output format is automatically selected based on the runtime environment:
// human-readable text for terminals, JSON for non-terminal output.
package logger

import (
	"fmt"
	"log/slog"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	"golang.org/x/term"
)

// Logger wraps slog.Logger to add automatic source location information
// to all log messages. It embeds slog.Logger and provides the same logging
// methods (Debug, Info, Warn, Error) but enriches each message with the
// file name, line number, and function name of the caller.
type Logger struct {
	*slog.Logger
}

// AsSlog returns the underlying slog.Logger for use with APIs that require
// a standard slog.Logger instance rather than the custom Logger type.
func (l *Logger) AsSlog() *slog.Logger {
	return l.Logger
}

// New creates a new Logger with an appropriate handler based on the runtime
// environment. If stdout is a terminal, it uses a human-readable text format;
// otherwise, it outputs JSON for structured log aggregation. The log level
// defaults to Info, but can be set to Debug by including "routewatch" in the
// DEBUG environment variable.
func New() *Logger {
	level := slog.LevelInfo
	if debug := os.Getenv("DEBUG"); strings.Contains(debug, "routewatch") {
		level = slog.LevelDebug
	}

	opts := &slog.HandlerOptions{
		Level: level,
	}

	var handler slog.Handler
	if term.IsTerminal(int(os.Stdout.Fd())) {
		// Terminal, use text
		handler = slog.NewTextHandler(os.Stdout, opts)
	} else {
		// Not a terminal, use JSON
		handler = slog.NewJSONHandler(os.Stdout, opts)
	}

	return &Logger{Logger: slog.New(handler)}
}

// sourceSkipLevel defines the number of call stack frames to skip when
// determining the caller's source location. This accounts for the logger
// method itself and the getSourceAttrs helper function.
const sourceSkipLevel = 2

// getSourceAttrs returns attributes for the calling source location
func getSourceAttrs() []slog.Attr {
	pc, file, line, ok := runtime.Caller(sourceSkipLevel)
	if !ok {
		return nil
	}

	// Get just the filename without the full path
	file = filepath.Base(file)

	// Get the function name
	fn := runtime.FuncForPC(pc)
	var funcName string
	if fn != nil {
		funcName = filepath.Base(fn.Name())
	}

	attrs := []slog.Attr{
		slog.String("source", fmt.Sprintf("%s:%d", file, line)),
	}

	if funcName != "" {
		attrs = append(attrs, slog.String("func", funcName))
	}

	return attrs
}

// Debug logs a message at debug level with automatic source location tracking.
// Additional structured attributes can be passed as key-value pairs in args.
// Debug messages are only output when the DEBUG environment variable contains
// "routewatch".
func (l *Logger) Debug(msg string, args ...any) {
	sourceAttrs := getSourceAttrs()
	allArgs := make([]any, 0, len(args)+len(sourceAttrs)*2)

	// Add source attributes first
	for _, attr := range sourceAttrs {
		allArgs = append(allArgs, attr)
	}

	// Add user args
	allArgs = append(allArgs, args...)

	l.Logger.Debug(msg, allArgs...)
}

// Info logs a message at info level with automatic source location tracking.
// Additional structured attributes can be passed as key-value pairs in args.
func (l *Logger) Info(msg string, args ...any) {
	sourceAttrs := getSourceAttrs()
	allArgs := make([]any, 0, len(args)+len(sourceAttrs)*2)

	// Add source attributes first
	for _, attr := range sourceAttrs {
		allArgs = append(allArgs, attr)
	}

	// Add user args
	allArgs = append(allArgs, args...)

	l.Logger.Info(msg, allArgs...)
}

// Warn logs a message at warn level with automatic source location tracking.
// Additional structured attributes can be passed as key-value pairs in args.
func (l *Logger) Warn(msg string, args ...any) {
	sourceAttrs := getSourceAttrs()
	allArgs := make([]any, 0, len(args)+len(sourceAttrs)*2)

	// Add source attributes first
	for _, attr := range sourceAttrs {
		allArgs = append(allArgs, attr)
	}

	// Add user args
	allArgs = append(allArgs, args...)

	l.Logger.Warn(msg, allArgs...)
}

// Error logs a message at error level with automatic source location tracking.
// Additional structured attributes can be passed as key-value pairs in args.
func (l *Logger) Error(msg string, args ...any) {
	sourceAttrs := getSourceAttrs()
	allArgs := make([]any, 0, len(args)+len(sourceAttrs)*2)

	// Add source attributes first
	for _, attr := range sourceAttrs {
		allArgs = append(allArgs, attr)
	}

	// Add user args
	allArgs = append(allArgs, args...)

	l.Logger.Error(msg, allArgs...)
}

// With returns a new Logger with additional structured attributes that will
// be included in all subsequent log messages. The args parameter accepts
// key-value pairs in the same format as the logging methods.
func (l *Logger) With(args ...any) *Logger {
	return &Logger{Logger: l.Logger.With(args...)}
}

// WithGroup returns a new Logger that adds the specified group name as a
// prefix to all attribute keys in subsequent log messages. This is useful
// for organizing related attributes under a common namespace.
func (l *Logger) WithGroup(name string) *Logger {
	return &Logger{Logger: l.Logger.WithGroup(name)}
}
