package logging

import (
	"context"
	"fmt"
	"io"
	"log/slog"
	"strings"

	mcptoolkitlogging "github.com/giantswarm/mcp-toolkit/logging"
	"github.com/go-logr/logr"
	ctrl "sigs.k8s.io/controller-runtime"
)

// Shutdown drains the underlying OpenTelemetry LoggerProvider on
// graceful exit. In non-OTLP modes it is a no-op closure. Safe to call
// more than once and from multiple goroutines.
type Shutdown func(ctx context.Context) error

// LogLevel defines the severity of the log entry.
type LogLevel int

const (
	LevelDebug LogLevel = iota
	LevelInfo
	LevelWarn
	LevelError
)

func (l LogLevel) SlogLevel() slog.Level {
	switch l {
	case LevelDebug:
		return slog.LevelDebug
	case LevelInfo:
		return slog.LevelInfo
	case LevelWarn:
		return slog.LevelWarn
	case LevelError:
		return slog.LevelError
	default:
		return slog.LevelInfo // Default to INFO for unknown
	}
}

var defaultLogger *slog.Logger

// initControllerRuntimeLogger initializes the controller-runtime logger using the provided slog handler.
// This must be called before any controller-runtime operations (informers, caches, etc.) are used,
// otherwise controller-runtime will print warnings about the logger not being initialized and
// status sync operations may fail.
//
// The function creates a logr.Logger from the slog handler and sets it as the controller-runtime
// global logger via ctrl.SetLogger(). This ensures that controller-runtime logs are properly
// routed through the muster logging infrastructure.
func initControllerRuntimeLogger(handler slog.Handler) {
	if handler == nil {
		return
	}

	// Create a logr.Logger from the slog handler
	// logr.FromSlogHandler is available in logr v1.3.0+
	logrLogger := logr.FromSlogHandler(handler)

	// Set the controller-runtime logger
	// This must be called before any controller operations to avoid warnings
	ctrl.SetLogger(logrLogger)
}

// InitForCLI initializes the logging system for CLI mode with text
// output, no OpenTelemetry plumbing. Use Init from the serve
// composition root for OTLP-aware initialisation.
//
// Args:
//   - filterLevel: minimum log level to output (Debug, Info, Warn, Error)
//   - output: writer for log output (typically os.Stdout or os.Stderr)
func InitForCLI(filterLevel LogLevel, output io.Writer) {
	// CLI mode never hits the OTLP branch, so the returned Shutdown
	// is a no-op closure and the error is always nil.
	logger, _, _ := mcptoolkitlogging.Init(context.Background(),
		mcptoolkitlogging.WithFormat(mcptoolkitlogging.FormatText),
		mcptoolkitlogging.WithLevel(filterLevel.SlogLevel()),
		mcptoolkitlogging.WithOutput(output),
	)
	defaultLogger = logger
	slog.SetDefault(logger)
	initControllerRuntimeLogger(logger.Handler())
}

// Init initialises the logging system in OpenTelemetry-aware mode and
// returns a Shutdown that drains the LoggerProvider on graceful exit.
//
// When any of OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
// OTEL_EXPORTER_OTLP_ENDPOINT, or OTEL_LOGS_EXPORTER is set, log
// records flow through OTLP and carry the active span's TraceID and
// SpanID (the OTel SDK pulls SpanContext from the ctx passed into
// InfoCtx / InfoWithAttrsCtx / etc.). Otherwise the handler is JSON
// inside a Kubernetes pod (KUBERNETES_SERVICE_HOST set), text
// otherwise — and Shutdown is a no-op closure.
//
// serviceName and serviceVersion become semconv.ServiceName /
// semconv.ServiceVersion on the OTel LoggerProvider's Resource;
// standard env overrides (OTEL_SERVICE_NAME, OTEL_RESOURCE_ATTRIBUTES)
// take precedence.
func Init(ctx context.Context, filterLevel LogLevel, output io.Writer, serviceName, serviceVersion string) (Shutdown, error) {
	logger, shutdown, err := mcptoolkitlogging.Init(ctx,
		mcptoolkitlogging.WithLevel(filterLevel.SlogLevel()),
		mcptoolkitlogging.WithOutput(output),
		mcptoolkitlogging.WithLoggerName("github.com/giantswarm/muster"),
		mcptoolkitlogging.WithServiceName(serviceName),
		mcptoolkitlogging.WithServiceVersion(serviceVersion),
	)
	if err != nil {
		return nil, fmt.Errorf("init toolkit logging: %w", err)
	}
	defaultLogger = logger
	slog.SetDefault(logger)
	initControllerRuntimeLogger(logger.Handler())
	return Shutdown(shutdown), nil
}

func logInternal(ctx context.Context, level LogLevel, subsystem string, err error, messageFmt string, args ...interface{}) {
	// Check if the level is enabled by the configured handler before proceeding.
	if defaultLogger == nil || !defaultLogger.Enabled(ctx, level.SlogLevel()) {
		return
	}

	msg := messageFmt
	if len(args) > 0 {
		msg = fmt.Sprintf(messageFmt, args...)
	}

	var slogAttrs []slog.Attr
	slogAttrs = append(slogAttrs, slog.String("subsystem", subsystem))
	if err != nil {
		slogAttrs = append(slogAttrs, slog.String("error", err.Error()))
	}

	defaultLogger.LogAttrs(ctx, level.SlogLevel(), msg, slogAttrs...)
}

// Debug logs a debug message.
func Debug(subsystem string, messageFmt string, args ...interface{}) {
	logInternal(context.Background(), LevelDebug, subsystem, nil, messageFmt, args...)
}

// DebugCtx is like Debug but threads ctx through the slog Handler so an
// active span's TraceID and SpanID are attached to the record in OTLP
// mode.
func DebugCtx(ctx context.Context, subsystem string, messageFmt string, args ...interface{}) {
	logInternal(ctx, LevelDebug, subsystem, nil, messageFmt, args...)
}

// DebugWithAttrs logs a debug message with additional structured attributes.
func DebugWithAttrs(subsystem string, msg string, attrs ...slog.Attr) {
	logWithAttrs(context.Background(), slog.LevelDebug, subsystem, msg, attrs...)
}

// DebugWithAttrsCtx is like DebugWithAttrs but threads ctx through the
// slog Handler so an active span's TraceID and SpanID are attached to
// the record in OTLP mode.
func DebugWithAttrsCtx(ctx context.Context, subsystem string, msg string, attrs ...slog.Attr) {
	logWithAttrs(ctx, slog.LevelDebug, subsystem, msg, attrs...)
}

// Info logs an informational message.
func Info(subsystem string, messageFmt string, args ...interface{}) {
	logInternal(context.Background(), LevelInfo, subsystem, nil, messageFmt, args...)
}

// InfoCtx is like Info but threads ctx through the slog Handler so an
// active span's TraceID and SpanID are attached to the record in OTLP
// mode.
func InfoCtx(ctx context.Context, subsystem string, messageFmt string, args ...interface{}) {
	logInternal(ctx, LevelInfo, subsystem, nil, messageFmt, args...)
}

// InfoWithAttrs logs an informational message with additional structured attributes.
func InfoWithAttrs(subsystem string, msg string, attrs ...slog.Attr) {
	logWithAttrs(context.Background(), slog.LevelInfo, subsystem, msg, attrs...)
}

// InfoWithAttrsCtx is like InfoWithAttrs but threads ctx through the
// slog Handler so an active span's TraceID and SpanID are attached to
// the record in OTLP mode.
func InfoWithAttrsCtx(ctx context.Context, subsystem string, msg string, attrs ...slog.Attr) {
	logWithAttrs(ctx, slog.LevelInfo, subsystem, msg, attrs...)
}

// Warn logs a warning message.
func Warn(subsystem string, messageFmt string, args ...interface{}) {
	logInternal(context.Background(), LevelWarn, subsystem, nil, messageFmt, args...)
}

// WarnCtx is like Warn but threads ctx through the slog Handler so an
// active span's TraceID and SpanID are attached to the record in OTLP
// mode.
func WarnCtx(ctx context.Context, subsystem string, messageFmt string, args ...interface{}) {
	logInternal(ctx, LevelWarn, subsystem, nil, messageFmt, args...)
}

// WarnWithAttrs logs a warning message with additional structured attributes.
func WarnWithAttrs(subsystem string, msg string, attrs ...slog.Attr) {
	logWithAttrs(context.Background(), slog.LevelWarn, subsystem, msg, attrs...)
}

// WarnWithAttrsCtx is like WarnWithAttrs but threads ctx through the
// slog Handler so an active span's TraceID and SpanID are attached to
// the record in OTLP mode.
func WarnWithAttrsCtx(ctx context.Context, subsystem string, msg string, attrs ...slog.Attr) {
	logWithAttrs(ctx, slog.LevelWarn, subsystem, msg, attrs...)
}

func logWithAttrs(ctx context.Context, level slog.Level, subsystem string, msg string, attrs ...slog.Attr) {
	if defaultLogger == nil || !defaultLogger.Enabled(ctx, level) {
		return
	}
	allAttrs := make([]slog.Attr, 0, len(attrs)+1)
	allAttrs = append(allAttrs, slog.String("subsystem", subsystem))
	allAttrs = append(allAttrs, attrs...)
	defaultLogger.LogAttrs(ctx, level, msg, allAttrs...)
}

// Error logs an error message.
func Error(subsystem string, err error, messageFmt string, args ...interface{}) {
	logInternal(context.Background(), LevelError, subsystem, err, messageFmt, args...)
}

// ErrorCtx is like Error but threads ctx through the slog Handler so an
// active span's TraceID and SpanID are attached to the record in OTLP
// mode.
func ErrorCtx(ctx context.Context, subsystem string, err error, messageFmt string, args ...interface{}) {
	logInternal(ctx, LevelError, subsystem, err, messageFmt, args...)
}

// TruncateIdentifier returns a truncated identifier (user subject, session ID, etc.)
// for secure logging. This prevents full identifiers from appearing in logs while
// still providing enough context for debugging correlation.
// Format: first 8 chars + "..." (e.g., "abc12345...")
func TruncateIdentifier(id string) string {
	if len(id) <= 8 {
		return id
	}
	return id[:8] + "..."
}

// TransportSessionID returns a slog.Attr with key "transportSessionID" and a truncated MCP transport session ID.
// The last UUID group is replaced with "..." to preserve enough of the ID for log
// correlation while avoiding logging the full value.
// Format: "mcp-session-ffa8afa1-eb56-4692-98d2-d6dbc99d4a06" → "mcp-session-ffa8afa1-eb56-4692-98d2-..."
func TransportSessionID(id string) slog.Attr {
	truncated := id
	if lastDash := strings.LastIndex(id, "-"); lastDash > 0 {
		truncated = id[:lastDash+1] + "..."
	}
	return slog.String("transportSessionID", truncated)
}

// AuditEvent represents a structured audit log event for security-sensitive operations.
// These events can be collected by external audit systems for compliance monitoring.
type AuditEvent struct {
	// Action is the type of action being audited (e.g., "token_exchange", "auth_login")
	Action string
	// Outcome indicates whether the action succeeded or failed
	Outcome string // "success" or "failure"
	// Subject is the truncated user subject (sub claim from JWT)
	Subject string
	// UserID is the truncated user identifier (from JWT sub claim or extracted user ID)
	UserID string
	// Target is the target of the action (e.g., server name, endpoint)
	Target string
	// Details provides additional context-specific information
	Details string
	// Error contains the error message if Outcome is "failure"
	Error string
}

// Audit logs a structured audit event for security-sensitive operations.
// Audit events are always logged at INFO level and include a special [AUDIT] prefix
// to make them easily filterable by log aggregation systems.
//
// Example output:
// [AUDIT] action=token_exchange outcome=success subject=abc12345... user=xyz789... target=mcp-kubernetes
func Audit(event AuditEvent) {
	// Pre-allocate with expected capacity for efficiency
	parts := make([]string, 0, 7)
	parts = append(parts, "action="+event.Action)
	parts = append(parts, "outcome="+event.Outcome)
	if event.Subject != "" {
		parts = append(parts, "subject="+event.Subject)
	}
	if event.UserID != "" {
		parts = append(parts, "user="+event.UserID)
	}
	if event.Target != "" {
		parts = append(parts, "target="+event.Target)
	}
	if event.Details != "" {
		parts = append(parts, "details="+event.Details)
	}
	if event.Error != "" {
		parts = append(parts, "error="+event.Error)
	}

	logInternal(context.Background(), LevelInfo, "AUDIT", nil, "[AUDIT] %s", strings.Join(parts, " "))
}
