package auth

import (
	"context"
	"net/http"
	"strings"

	"github.com/wangjia/pangolin/server/internal/codes"
)

// ctxKey is this package's private context-key type for values other than the
// numeric user id (which is shared with the codes module — see below).
type ctxKey string

const (
	// ctxKeyUserUUID stores the authenticated user's UUID (the JWT subject).
	ctxKeyUserUUID ctxKey = "user_uuid"
	// ctxKeyClaims stores the full *Claims for handlers that need jti/exp.
	ctxKeyClaims ctxKey = "claims"
)

// The numeric user id is injected under codes.CtxKeyUserID so that the codes
// module (and any other module sharing that exported key) reads the same value
// without an import cycle — codes deliberately exports the key for this purpose.

// RequireAuth returns middleware that enforces a valid Bearer access token on
// every wrapped route. On success it injects the numeric user id, the user
// UUID, and the parsed claims into the request context. This is the auth base
// for all protected /v1 routes (everything except the auth group).
func RequireAuth(tm *TokenManager) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			token, ok := bearerToken(r)
			if !ok {
				writeAPIErr(w, ErrUnauthorized, 0)
				return
			}
			claims, err := tm.ParseAccess(token)
			if err != nil {
				writeAPIErr(w, ErrUnauthorized, 0)
				return
			}

			ctx := r.Context()
			ctx = context.WithValue(ctx, codes.CtxKeyUserID, claims.UID)
			ctx = context.WithValue(ctx, ctxKeyUserUUID, claims.Subject)
			ctx = context.WithValue(ctx, ctxKeyClaims, claims)
			next.ServeHTTP(w, r.WithContext(ctx))
		})
	}
}

// bearerToken extracts the token from an "Authorization: Bearer <token>" header.
func bearerToken(r *http.Request) (string, bool) {
	h := r.Header.Get("Authorization")
	if h == "" {
		return "", false
	}
	const prefix = "Bearer "
	if len(h) <= len(prefix) || !strings.EqualFold(h[:len(prefix)], prefix) {
		return "", false
	}
	token := strings.TrimSpace(h[len(prefix):])
	if token == "" {
		return "", false
	}
	return token, true
}

// UserIDFromContext returns the numeric user id injected by RequireAuth.
func UserIDFromContext(ctx context.Context) (int64, bool) {
	v, ok := ctx.Value(codes.CtxKeyUserID).(int64)
	return v, ok && v != 0
}

// UserUUIDFromContext returns the user UUID injected by RequireAuth.
func UserUUIDFromContext(ctx context.Context) (string, bool) {
	v, ok := ctx.Value(ctxKeyUserUUID).(string)
	return v, ok && v != ""
}

// ClaimsFromContext returns the parsed access-token claims injected by RequireAuth.
func ClaimsFromContext(ctx context.Context) (*Claims, bool) {
	v, ok := ctx.Value(ctxKeyClaims).(*Claims)
	return v, ok
}
