package auth

import (
	"net/http"
	"slices"

	"gitea.hostxtra.co.uk/mrhid6/vantage/admin/internal/db"
	"gitea.hostxtra.co.uk/mrhid6/vantage/admin/internal/models"
	"github.com/gin-gonic/gin"
	"go.mongodb.org/mongo-driver/v2/bson"
)

const ctxSession = "admin_session_obj"

// Load returns the caller's session, or nil. Exported because the session probe
// in api/ needs to read a session without requiring one.
func Load(c *gin.Context) *Session {
	id, err := c.Cookie(CookieName)
	if err != nil || id == "" {
		return nil
	}
	s, err := Get(c.Request.Context(), id)
	if err != nil {
		return nil
	}
	return s
}

// Current returns the session, or nil.
func Current(c *gin.Context) *Session {
	if v, ok := c.Get(ctxSession); ok {
		if s, ok := v.(*Session); ok {
			return s
		}
	}
	return nil
}

func RequireStaff() gin.HandlerFunc {
	return func(c *gin.Context) {
		s := Load(c)
		if s == nil || s.Kind != KindStaff {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "sign in required"})
			return
		}
		c.Set(ctxSession, s)
		c.Next()
	}
}

// RequireCustomer admits both cloud and self-hosted customers. Every handler
// behind it scopes by AccountID via the helper in api/customer.go — never by
// remembering to filter.
func RequireCustomer() gin.HandlerFunc {
	return func(c *gin.Context) {
		s := Load(c)
		if s == nil || s.Kind != KindCustomer || s.AccountID == "" {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "sign in required"})
			return
		}
		c.Set(ctxSession, s)
		c.Next()
	}
}

const ctxCustomerUser = "admin_customer_user"

// CurrentUser returns the calling customer's own row, loaded once per request
// by RequireAccountRole.
//
// It is nil behind RequireCustomer alone. A handler that needs the role must
// sit behind RequireAccountRole, which is the only thing that loads it.
func CurrentUser(c *gin.Context) *models.CustomerUser {
	if v, ok := c.Get(ctxCustomerUser); ok {
		if u, ok := v.(*models.CustomerUser); ok {
			return u
		}
	}
	return nil
}

// RequireAccountRole admits a customer holding one of the given account roles.
//
// The role is read from the database on every request rather than carried in
// the session. A session lives 24 hours; a demotion that only takes effect
// when someone signs out again is not a demotion.
func RequireAccountRole(roles ...string) gin.HandlerFunc {
	return func(c *gin.Context) {
		s := Current(c)
		if s == nil {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "sign in required"})
			return
		}
		var u models.CustomerUser
		if err := db.Admin("customer_users").FindOne(c.Request.Context(),
			bson.M{"user_id": s.UserID}).Decode(&u); err != nil {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "sign in required"})
			return
		}
		if !slices.Contains(roles, u.AccountRole) {
			// 403 rather than 404 here: this is the caller's OWN account, so
			// there is no existence to disclose — the 404 rule protects other
			// accounts' resources, not the caller's view of their own.
			c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
				"error": "your account role does not allow this"})
			return
		}
		c.Set(ctxCustomerUser, &u)
		c.Next()
	}
}
