package authentication

import (
	"context"
	"errors"
	"net/http"

	"git.kirsle.net/apps/gophertype/pkg/console"
	"git.kirsle.net/apps/gophertype/pkg/models"
	"git.kirsle.net/apps/gophertype/pkg/session"
)

// CurrentUser returns the currently logged-in user in the browser session.
func CurrentUser(r *http.Request) (models.User, error) {
	sess := session.Get(r)
	if loggedIn, ok := sess.Values["logged-in"].(bool); ok && loggedIn {
		if id, ok := sess.Values["user-id"].(int); ok && id > 0 {
			user, err := models.Users.GetUserByID(id)
			if err != nil {
				console.Error("CurrentUser: user '%d' was not found in DB! Logging out the session", id)
				delete(sess.Values, "user-id")
				delete(sess.Values, "logged-in")
				return user, err
			}
			return user, err
		}
		return models.User{}, errors.New("not logged in")
	}
	return models.User{}, errors.New("not logged in")
}

// Login logs the browser session in as the user.
func Login(w http.ResponseWriter, r *http.Request, user models.User) {
	sess := session.Get(r)

	sess.Values["logged-in"] = true
	sess.Values["user-id"] = int(user.ID)
	if err := sess.Save(r, w); err != nil {
		console.Error("Login() Session error: " + err.Error())
	}
}

// Logout logs the current user out.
func Logout(w http.ResponseWriter, r *http.Request) {
	sess := session.Get(r)
	sess.Values["logged-in"] = false
	sess.Values["user-id"] = 0
	sess.Save(r, w)
}

// LoggedIn returns whether the session is logged in as a user.
func LoggedIn(r *http.Request) bool {
	sess := session.Get(r)
	if v, ok := sess.Values["logged-in"].(bool); ok && v == true {
		return true
	}
	return false
}

// LoginRequired is a middleware for authenticated endpoints.
func LoginRequired(next http.Handler) http.Handler {
	middleware := func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()
		if user, ok := ctx.Value(session.UserKey).(models.User); ok {
			if user.ID > 0 {
				next.ServeHTTP(w, r)
				return
			}
		}

		// Redirect to the login page.
		w.Header().Set("Location", "/login?next="+r.URL.Path)
		w.WriteHeader(http.StatusFound)
	}

	return http.HandlerFunc(middleware)
}

// Middleware checks the authentication and loads the user onto the request context.
func Middleware(next http.Handler) http.Handler {
	middleware := func(w http.ResponseWriter, r *http.Request) {
		user, err := CurrentUser(r)
		if err != nil {
			// User not logged in, go to next middleware.
			next.ServeHTTP(w, r)
			return
		}

		// Put the CurrentUser into the request context.
		ctx := context.WithValue(r.Context(), session.UserKey, user)
		next.ServeHTTP(w, r.WithContext(ctx))
	}

	return http.HandlerFunc(middleware)
}
