package logging

import (
	"context"
	"encoding/json"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"gopkg.in/lumberjack.v2"
)

// Config holds logging configuration, mirrors config.LoggingConfig.
type Config struct {
	Level      string
	File       string
	MaxSizeMB  int
	MaxBackups int
	MaxAgeDays int
	Compress   bool
}

// Setup initializes the global zerolog logger.
// - File set: JSON → lumberjack rotating file
// - File empty + TTY: colored ConsoleWriter → stderr
// - File empty + not TTY: JSON → stderr (for systemd journal)
func Setup(cfg Config) zerolog.Logger {
	// Parse log level
	level, err := zerolog.ParseLevel(cfg.Level)
	if err != nil || cfg.Level == "" {
		level = zerolog.InfoLevel
	}
	zerolog.SetGlobalLevel(level)
	zerolog.TimeFieldFormat = time.RFC3339

	var logger zerolog.Logger

	if cfg.File != "" {
		// Production: JSON to rotating file
		jack := &lumberjack.Logger{
			Filename:   cfg.File,
			MaxSize:    cfg.MaxSizeMB,
			MaxBackups: cfg.MaxBackups,
			MaxAge:     cfg.MaxAgeDays,
			Compress:   cfg.Compress,
		}
		logger = zerolog.New(jack).With().Timestamp().Caller().Logger()
	} else {
		fi, err := os.Stderr.Stat()
		isTTY := err == nil && (fi.Mode()&os.ModeCharDevice) != 0
		if isTTY {
			// Dev mode: colored console
			cw := zerolog.ConsoleWriter{
				Out:        os.Stderr,
				TimeFormat: time.RFC3339,
			}
			logger = zerolog.New(cw).With().Timestamp().Caller().Logger()
		} else {
			// Systemd journal: JSON to stderr
			logger = zerolog.New(os.Stderr).With().Timestamp().Caller().Logger()
		}
	}

	// Set global
	log.Logger = logger
	return logger
}

// GinRequestLogger returns a Gin middleware that logs every request with zerolog.
// Logs AFTER the request completes.
// Level: Info for 2xx, Warn for 4xx, Error for 5xx
func GinRequestLogger() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		path := c.Request.URL.Path
		method := c.Request.Method

		c.Next()

		status := c.Writer.Status()
		latencyMs := float64(time.Since(start).Microseconds()) / 1000.0
		clientIP := c.ClientIP()
		requestID := c.Writer.Header().Get("X-Request-Id")
		if requestID == "" {
			requestID = c.GetHeader("x-client-request-id")
		}

		evt := log.Logger.WithLevel(statusLevel(status)).
			Str("method", method).
			Str("path", path).
			Int("status", status).
			Float64("latency_ms", latencyMs).
			Str("client_ip", clientIP)

		if requestID != "" {
			evt = evt.Str("request_id", requestID)
		}

		evt.Msg("request")
	}
}

func statusLevel(status int) zerolog.Level {
	switch {
	case status >= 500:
		return zerolog.ErrorLevel
	case status >= 400:
		return zerolog.WarnLevel
	default:
		return zerolog.InfoLevel
	}
}

// FromContext returns the zerolog logger from context, or the global logger.
func FromContext(ctx context.Context) *zerolog.Logger {
	l := zerolog.Ctx(ctx)
	if l.GetLevel() == zerolog.Disabled {
		return &log.Logger
	}
	return l
}

// RedactHeaders serializes HTTP headers to a JSON string,
// replacing Authorization and x-api-key values with "***".
func RedactHeaders(h http.Header) string {
	redacted := make(map[string]string, len(h))
	for k, v := range h {
		key := strings.ToLower(k)
		if key == "authorization" || key == "x-api-key" {
			redacted[k] = "***"
		} else {
			redacted[k] = strings.Join(v, ", ")
		}
	}
	b, _ := json.Marshal(redacted)
	return string(b)
}
