package middleware

import (
	"context"
	"net/http"
	"net/url"
	"time"

	"code.nonshy.com/nonshy/website/pkg/config"
	"code.nonshy.com/nonshy/website/pkg/controller/photo"
	"code.nonshy.com/nonshy/website/pkg/log"
	"code.nonshy.com/nonshy/website/pkg/models"
	"code.nonshy.com/nonshy/website/pkg/session"
	"code.nonshy.com/nonshy/website/pkg/templates"
)

// LoginRequired middleware.
func LoginRequired(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

		// User must be logged in.
		user, err := session.CurrentUser(r)
		if err != nil {
			log.Error("LoginRequired: %s", err)
			session.FlashError(w, r, "You must be signed in to view this page.")
			templates.Redirect(w, "/login?next="+url.QueryEscape(r.URL.String()))
			return
		}

		// Are they banned?
		if user.Status == models.UserStatusBanned {
			session.LogoutUser(w, r)
			session.FlashError(w, r, "Your account has been banned and you are now logged out.")
			templates.Redirect(w, "/")
			return
		}

		// Is their account disabled?
		if DisabledAccount(user, w, r) {
			return
		}

		// Is the site under a Maintenance Mode restriction?
		if MaintenanceMode(user, w, r) {
			return
		}

		// Ping LastLoginAt for long lived sessions, but not if impersonated.
		if time.Since(user.LastLoginAt) > config.LastLoginAtCooldown && !session.Impersonated(r) {
			user.LastLoginAt = time.Now()
			if err := user.Save(); err != nil {
				log.Error("LoginRequired: couldn't refresh LastLoginAt for user %s: %s", user.Username, err)
			}
		}

		// Ask the user for their birthdate?
		if AgeGate(user, w, r) {
			return
		}

		// Stick the CurrentUser in the request context so future calls to session.CurrentUser can read it.
		ctx := context.WithValue(r.Context(), session.CurrentUserKey, user)
		handler.ServeHTTP(w, r.WithContext(ctx))
	})
}

// AdminRequired middleware.
func AdminRequired(scope string, handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

		// User must be logged in.
		currentUser, err := session.CurrentUser(r)
		if err != nil {
			log.Error("AdminRequired: %s", err)
			session.FlashError(w, r, "You must be signed in to view this page.")
			templates.Redirect(w, "/login?next="+url.QueryEscape(r.URL.String()))
			return
		}

		// Stick the CurrentUser in the request context so future calls to session.CurrentUser can read it.
		ctx := context.WithValue(r.Context(), session.CurrentUserKey, currentUser)

		// Admin required.
		if !currentUser.IsAdmin {
			errhandler := templates.MakeErrorPage("Admin Required", "You do not have permission for this page.", http.StatusForbidden)
			errhandler.ServeHTTP(w, r.WithContext(ctx))
			return
		}

		// Ensure the admin scope.
		if scope != "" && !currentUser.HasAdminScope(scope) {
			errhandler := templates.MakeErrorPage(
				"Admin Scope Required",
				"Missing required admin scope: "+scope,
				http.StatusForbidden,
			)
			errhandler.ServeHTTP(w, r.WithContext(ctx))
			return
		}

		handler.ServeHTTP(w, r.WithContext(ctx))
	})
}

// CertRequired middleware: like LoginRequired but user must also have their verification pic certified.
func CertRequired(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

		// User must be logged in.
		currentUser, err := session.CurrentUser(r)
		if err != nil {
			log.Error("LoginRequired: %s", err)
			session.FlashError(w, r, "You must be signed in to view this page.")
			templates.Redirect(w, "/login?next="+url.QueryEscape(r.URL.String()))
			return
		}

		// Are they banned?
		if currentUser.Status == models.UserStatusBanned {
			session.LogoutUser(w, r)
			session.FlashError(w, r, "Your account has been banned and you are now logged out.")
			templates.Redirect(w, "/")
			return
		}

		// Is their account disabled?
		if DisabledAccount(currentUser, w, r) {
			return
		}

		// Is the site under a Maintenance Mode restriction?
		if MaintenanceMode(currentUser, w, r) {
			return
		}

		// User must be certified.
		if !currentUser.Certified || currentUser.ProfilePhoto.ID == 0 {
			log.Error("CertRequired: user is not certified")
			photo.CertificationRequiredError().ServeHTTP(w, r)
			return
		}

		// Ask the user for their birthdate?
		if AgeGate(currentUser, w, r) {
			return
		}

		handler.ServeHTTP(w, r)
	})
}
