package infra

import (
	"context"
	"database/sql"
	"net/http"
	"strings"
)

// SessionCookieConfig configura el middleware de autenticacion por cookie/Bearer.
type SessionCookieConfig struct {
	DB         *sql.DB
	CookieName string   // nombre de la cookie, ej: "kanban_session"
	SkipPaths  []string // prefijos de path que no requieren auth
	UserCtxKey any      // clave tipada para inyectar el userID en el contexto
}

// HTTPSessionCookieMiddleware retorna un Middleware que valida la sesion del
// request via cookie o header Authorization: Bearer.
// Si el path esta en SkipPaths delega sin validar.
// Si el token es valido inyecta el userID en r.Context() con cfg.UserCtxKey.
// Responde 401 JSON si falta el token o la sesion es invalida.
func HTTPSessionCookieMiddleware(cfg SessionCookieConfig) Middleware {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// 1. Skip paths
			for _, prefix := range cfg.SkipPaths {
				if strings.HasPrefix(r.URL.Path, prefix) {
					next.ServeHTTP(w, r)
					return
				}
			}

			// 2. Extraer token: primero cookie, luego Authorization header
			token := ""
			if c, err := r.Cookie(cfg.CookieName); err == nil {
				token = c.Value
			} else {
				auth := r.Header.Get("Authorization")
				if strings.HasPrefix(auth, "Bearer ") {
					token = strings.TrimPrefix(auth, "Bearer ")
				}
			}

			// 3. Sin token → 401
			if token == "" {
				HTTPErrorResponse(w, HTTPError{
					Status:  http.StatusUnauthorized,
					Code:    "unauthorized",
					Message: "session required",
				})
				return
			}

			// 4. Validar sesion
			session, err := SessionValidate(cfg.DB, token)
			if err != nil {
				HTTPErrorResponse(w, HTTPError{
					Status:  http.StatusUnauthorized,
					Code:    "invalid_session",
					Message: err.Error(),
				})
				return
			}

			// 5. Inyectar userID en contexto y delegar
			ctx := context.WithValue(r.Context(), cfg.UserCtxKey, session.UserID)
			next.ServeHTTP(w, r.WithContext(ctx))
		})
	}
}

// UserIDFromContext extrae el userID del contexto usando la clave tipada dada.
// Retorna ("", false) si no esta presente o el tipo no coincide.
func UserIDFromContext(ctx context.Context, key any) (string, bool) {
	v, ok := ctx.Value(key).(string)
	return v, ok
}
