package http

import (
	stdhttp "net/http"

	"gitea.dcglab.co.uk/steve/restic-manager/internal/server/ui"
	"gitea.dcglab.co.uk/steve/restic-manager/internal/store"
)

// rank maps each role to a numeric tier so 'A is at least B' becomes
// 'rank[A] >= rank[B] && both are known'. Unknown roles return 0 →
// fail-closed against either argument.
var roleRank = map[store.Role]int{
	store.RoleViewer:   1,
	store.RoleOperator: 2,
	store.RoleAdmin:    3,
}

// roleAtLeast reports whether `have` meets or exceeds `min` in the
// admin > operator > viewer hierarchy. Either side being an unknown
// role returns false.
func roleAtLeast(have, min store.Role) bool {
	h, hok := roleRank[have]
	m, mok := roleRank[min]
	if !hok || !mok {
		return false
	}
	return h >= m
}

// requireRole returns chi middleware that 403s any request whose
// session-resolved user doesn't meet the minimum role. Unauthenticated
// requests return 401 (JSON) or 303 → /login (HTML) so the caller
// gets a usable error rather than a confusing 403.
//
// The middleware re-reads the user row on every request — by the time
// you read this you might be tempted to cache; don't. SQLite's WAL
// makes the lookup cheap and admin-driven changes (disable, role
// change) need to land immediately.
func (s *Server) requireRole(min store.Role) func(stdhttp.Handler) stdhttp.Handler {
	return func(next stdhttp.Handler) stdhttp.Handler {
		return stdhttp.HandlerFunc(func(w stdhttp.ResponseWriter, r *stdhttp.Request) {
			u, ok := s.requireUser(r)
			if !ok {
				if isAPIPath(r) {
					writeJSONError(w, stdhttp.StatusUnauthorized, "unauthorised", "")
					return
				}
				stdhttp.Redirect(w, r, "/login", stdhttp.StatusSeeOther)
				return
			}
			if !roleAtLeast(u.Role, min) {
				if isAPIPath(r) {
					writeJSONError(w, stdhttp.StatusForbidden, "insufficient_role", "")
					return
				}
				renderForbiddenHTML(s, w, r, u, min)
				return
			}
			next.ServeHTTP(w, r)
		})
	}
}

// isAPIPath reports whether the path lives under /api/. Lets one
// middleware return JSON or HTML appropriately without two near-
// identical wrappers.
func isAPIPath(r *stdhttp.Request) bool {
	p := r.URL.Path
	return len(p) >= 5 && p[:5] == "/api/"
}

// renderForbiddenHTML emits a small "you don't have permission"
// panel inside the chrome so the user keeps their nav and can
// move away to a page they can see.
func renderForbiddenHTML(s *Server, w stdhttp.ResponseWriter, r *stdhttp.Request, u *store.User, min store.Role) {
	w.WriteHeader(stdhttp.StatusForbidden)
	view := s.baseView(r, &ui.User{ID: u.ID, Username: u.Username, Role: string(u.Role)})
	view.Title = "Forbidden · restic-manager"
	view.Page = struct {
		Required string
		Have     string
	}{Required: string(min), Have: string(u.Role)}
	if err := s.deps.UI.Render(w, "forbidden", view); err != nil {
		_, _ = w.Write([]byte("403 Forbidden — your role does not permit this page."))
	}
}
