package observability

import (
	"fmt"
	"net/http"
	"time"

	chimiddleware "github.com/go-chi/chi/v5/middleware"
	"github.com/gofrs/uuid"
	"github.com/sirupsen/logrus"
	"github.com/supabase/auth/internal/conf"
	"github.com/supabase/auth/internal/utilities"
)

func AddRequestID(globalConfig *conf.GlobalConfiguration) func(next http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		fn := func(w http.ResponseWriter, r *http.Request) {
			id := uuid.Must(uuid.NewV4()).String()
			if globalConfig.API.RequestIDHeader != "" {
				id = r.Header.Get(globalConfig.API.RequestIDHeader)
			}
			ctx := r.Context()
			ctx = utilities.WithRequestID(ctx, id)
			next.ServeHTTP(w, r.WithContext(ctx))
		}
		return http.HandlerFunc(fn)
	}
}

func NewStructuredLogger(logger *logrus.Logger, config *conf.GlobalConfiguration) func(next http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.URL.Path == "/health" {
				next.ServeHTTP(w, r)
			} else {
				chimiddleware.RequestLogger(&structuredLogger{logger, config})(next).ServeHTTP(w, r)
			}
		})
	}
}

type structuredLogger struct {
	Logger *logrus.Logger
	Config *conf.GlobalConfiguration
}

func (l *structuredLogger) NewLogEntry(r *http.Request) chimiddleware.LogEntry {
	referrer := utilities.GetReferrer(r, l.Config)
	e := &logEntry{Entry: logrus.NewEntry(l.Logger)}
	logFields := logrus.Fields{
		"component":   "api",
		"method":      r.Method,
		"path":        r.URL.Path,
		"remote_addr": utilities.GetIPAddress(r),
		"referer":     referrer,
	}

	if reqID := utilities.GetRequestID(r.Context()); reqID != "" {
		logFields["request_id"] = reqID
	}

	e.Entry = e.Entry.WithFields(logFields)
	return e
}

// logEntry implements the chiMiddleware.LogEntry interface
type logEntry struct {
	Entry *logrus.Entry
}

func (e *logEntry) Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{}) {
	fields := logrus.Fields{
		"status":   status,
		"duration": elapsed.Nanoseconds(),
	}

	errorCode := header.Get("x-sb-error-code")
	if errorCode != "" {
		fields["error_code"] = errorCode
	}

	entry := e.Entry.WithFields(fields)
	entry.Info("request completed")
	e.Entry = entry
}

func (e *logEntry) Panic(v interface{}, stack []byte) {
	entry := e.Entry.WithFields(logrus.Fields{
		"stack": string(stack),
		"panic": fmt.Sprintf("%+v", v),
	})
	entry.Error("request panicked")
	e.Entry = entry
}

func GetLogEntry(r *http.Request) *logEntry {
	l, _ := chimiddleware.GetLogEntry(r).(*logEntry)
	if l == nil {
		return &logEntry{Entry: logrus.NewEntry(logrus.StandardLogger())}
	}
	return l
}

func LogEntrySetField(r *http.Request, key string, value interface{}) {
	if l, ok := r.Context().Value(chimiddleware.LogEntryCtxKey).(*logEntry); ok {
		l.Entry = l.Entry.WithField(key, value)
	}
}

func LogEntrySetFields(r *http.Request, fields logrus.Fields) {
	if l, ok := r.Context().Value(chimiddleware.LogEntryCtxKey).(*logEntry); ok {
		l.Entry = l.Entry.WithFields(fields)
	}
}
