// Package logger provides logging functionality for the application.
// It includes a Gin middleware for logging requests and responses.
package logger

import (
	"context"
	"fmt"
	"log/slog"
	"net/http"
	"os"
	"runtime"
	"strings"
	"time"

	"github.com/gin-gonic/gin"

	"quantex.com/qfixpt/src/app/version"
	"quantex.com/qfixpt/src/common/logger/tint"
)

//nolint:gochecknoglobals // need to be global
var loggerWoNotifier *slog.Logger

func ErrorWoNotifier(msg string, args ...any) {
	if loggerWoNotifier == nil || !loggerWoNotifier.Enabled(context.Background(), slog.LevelError) {
		return
	}
	var pcs [1]uintptr
	runtime.Callers(2, pcs[:]) // skip [Callers, Infof]
	r := slog.NewRecord(time.Now(), slog.LevelError, fmt.Sprintf(msg, args...), pcs[0])
	err := loggerWoNotifier.Handler().Handle(context.Background(), r)
	if err != nil {
		// We can't use slog.Error here because it will try to send the message to the notifier
		// and will create a recursive loop
		slog.Info("error handling slog record in ErrorWoNotifier: " + err.Error())

		return
	}

	// loggerWoNotifier.Error(msg, args...)
}

func NewTextHandler(level slog.Level) slog.Handler {
	replace := func(_ []string, a slog.Attr) slog.Attr {
		if a.Key == slog.SourceKey {
			source, ok := a.Value.Any().(*slog.Source)
			if !ok {
				// handle the case when the assertion fails
				slog.Warn(fmt.Sprintf("unexpected type %T for slog.Source", a.Value.Any()))

				return a
			}
			// Just add a space before the file path to enable to open the file from the IDE
			p := strings.Split(source.File, strings.ToLower(version.AppName)+"/")
			if len(p) > 1 {
				source.File = " " + p[1]
			} else {
				source.File = " " + source.File
			}
		}

		if a.Key == slog.TimeKey {
			a.Value = slog.StringValue(a.Value.Time().Format("15:04:05.000000"))
		}

		return a
	}

	opts := &slog.HandlerOptions{
		AddSource:   true,
		Level:       level,
		ReplaceAttr: replace,
	}

	handler := slog.NewTextHandler(os.Stdout, opts)
	loggerWoNotifier = slog.New(handler)

	return handler
}

func NewTextHandlerTint(level slog.Level, lines tint.LinesCount) slog.Handler {
	handler := tint.NewHandler(os.Stdout, &tint.Options{
		AddSource:  true,
		Level:      level,
		TimeFormat: "15:04:05.000000",
		Lines:      lines,
	})

	loggerWoNotifier = slog.New(handler)

	return handler
}

func NewJSONHandler(level slog.Level) slog.Handler {
	opts := &slog.HandlerOptions{
		AddSource: true,
		Level:     level,
	}

	handler := slog.NewJSONHandler(os.Stdout, opts)
	loggerWoNotifier = slog.New(handler)

	return handler
}

// GinLoggerMiddleware creates a Gin middleware for logging using slog.
func GinLoggerMiddleware(logger *slog.Logger) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		start := time.Now()

		// Store request parameters
		params := []slog.Attr{
			slog.String("method", ctx.Request.Method),
			slog.String("path", ctx.Request.URL.Path),
			slog.Any("query", ctx.Request.URL.Query()),
			slog.Any("headers", ctx.Request.Header),
		}

		// Store the request body if needed
		if ctx.Request.Method == http.MethodPost || ctx.Request.Method == http.MethodPut {
			if body, err := ctx.GetRawData(); err == nil {
				params = append(params, slog.String("body", string(body)))
			}
		}

		// Add status and duration to parameters
		params = append(params,
			slog.Int("status", ctx.Writer.Status()),
			slog.String("duration", time.Since(start).String()),
		)

		// Log request details with slog
		logger.LogAttrs(context.Background(), slog.LevelInfo, "Request Details", params...)

		ctx.Next()
	}
}
