// Package logger provides a structured logger with source location tracking
package logger

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

	"golang.org/x/term"
)

// Logger wraps slog.Logger to add source location information
type Logger struct {
	*slog.Logger
}

// AsSlog returns the underlying slog.Logger
func (l *Logger) AsSlog() *slog.Logger {
	return l.Logger
}

// New creates a new logger with appropriate handler based on environment
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)}
}

const sourceSkipLevel = 2 // Skip levels for source location tracking

// 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 at debug level with source location
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 at info level with source location
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 at warn level with source location
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 at error level with source location
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 attributes
func (l *Logger) With(args ...any) *Logger {
	return &Logger{Logger: l.Logger.With(args...)}
}

// WithGroup returns a new logger with a group prefix
func (l *Logger) WithGroup(name string) *Logger {
	return &Logger{Logger: l.Logger.WithGroup(name)}
}
