package middleware

import (
	"context"
	"net/http"

	"codeberg.org/nextgo/dbots/internal/db"
	"codeberg.org/nextgo/dbots/internal/errorutil"
	"codeberg.org/nextgo/dbots/internal/token"
	"github.com/go-chi/render"
)

type contextKey string

// UserContextKey is exported so service routers can read the user from context.
var UserContextKey contextKey = "user"

// AuthMiddleware reads the PASETO session cookie, verifies it, checks it
// hasn't been revoked in the DB, then sets the *db.User on the context.
// Does NOT block unauthenticated requests — use AuthGuardMiddleware for that.
func AuthMiddleware(q *db.Queries, pasetoKeyHex string) func(http.Handler) http.Handler {
	key, err := token.KeyFromHex(pasetoKeyHex)
	if err != nil {
		panic("middleware: invalid PASETO key: " + err.Error())
	}

	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			c, err := r.Cookie("session")
			if err != nil {
				next.ServeHTTP(w, r)
				return
			}

			claims, err := token.VerifyToken(key, c.Value)
			if err != nil {
				// Expired or tampered — clear the cookie and continue as anonymous.
				http.SetCookie(w, &http.Cookie{Name: "session", MaxAge: -1, Path: "/"})
				next.ServeHTTP(w, r)
				return
			}

			// Check the session hasn't been revoked server-side.
			if _, err := q.GetSession(r.Context(), claims.JTI); err != nil {
				http.SetCookie(w, &http.Cookie{Name: "session", MaxAge: -1, Path: "/"})
				next.ServeHTTP(w, r)
				return
			}

			user, err := q.GetUser(r.Context(), claims.UserID)
			if err != nil {
				next.ServeHTTP(w, r)
				return
			}

			ctx := context.WithValue(r.Context(), UserContextKey, user)
			next.ServeHTTP(w, r.WithContext(ctx))
		})
	}
}

// AuthGuardMiddleware blocks requests where no authenticated user was set.
func AuthGuardMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if _, ok := r.Context().Value(UserContextKey).(*db.User); !ok {
			render.Render(w, r, errorutil.ErrUnauthorized)
			return
		}
		next.ServeHTTP(w, r)
	})
}

// GetUser returns the authenticated user from context.
// Only safe to call inside a route guarded by AuthGuardMiddleware.
func GetUser(ctx context.Context) *db.User {
	return ctx.Value(UserContextKey).(*db.User)
}
