package http

import (
	"bufio"
	"crypto/subtle"
	"errors"
	"log/slog"
	"net"
	stdhttp "net/http"
	"time"
)

type statusRecorder struct {
	stdhttp.ResponseWriter
	status int
}

func (r *statusRecorder) WriteHeader(status int) {
	r.status = status
	r.ResponseWriter.WriteHeader(status)
}

// Hijack lets WebSocket upgrades (e.g. the proto-socket route) take over the
// connection even though the response writer is wrapped for status logging.
func (r *statusRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	hijacker, ok := r.ResponseWriter.(stdhttp.Hijacker)
	if !ok {
		return nil, nil, errors.New("underlying ResponseWriter does not implement http.Hijacker")
	}
	return hijacker.Hijack()
}

func loggingMiddleware(logger *slog.Logger) func(stdhttp.Handler) stdhttp.Handler {
	return func(next stdhttp.Handler) stdhttp.Handler {
		return stdhttp.HandlerFunc(func(w stdhttp.ResponseWriter, r *stdhttp.Request) {
			start := time.Now()
			recorder := &statusRecorder{ResponseWriter: w, status: stdhttp.StatusOK}
			next.ServeHTTP(recorder, r)

			if logger != nil {
				logger.Info(
					"http request",
					"method", r.Method,
					"path", r.URL.Path,
					"status", recorder.status,
					"duration", time.Since(start).String(),
				)
			}
		})
	}
}

func basicAuthMiddleware(auth AuthConfig) func(stdhttp.Handler) stdhttp.Handler {
	if auth.Password == "" {
		return func(next stdhttp.Handler) stdhttp.Handler {
			return next
		}
	}
	if auth.Username == "" {
		auth.Username = "nomadcode"
	}

	return func(next stdhttp.Handler) stdhttp.Handler {
		return stdhttp.HandlerFunc(func(w stdhttp.ResponseWriter, r *stdhttp.Request) {
			username, password, ok := r.BasicAuth()
			if !ok || !secureCompare(username, auth.Username) || !secureCompare(password, auth.Password) {
				w.Header().Set("WWW-Authenticate", `Basic realm="nomadcode-core", charset="UTF-8"`)
				writeError(w, stdhttp.StatusUnauthorized, "authentication required")
				return
			}

			next.ServeHTTP(w, r)
		})
	}
}

func secureCompare(got, want string) bool {
	return subtle.ConstantTimeCompare([]byte(got), []byte(want)) == 1
}
