package middleware

import (
	"net/http"
	"os"
	"strings"

	configs "astrology-api/configs"
	"astrology-api/constants"
	usermodel "astrology-api/models/usermodel"
	"astrology-api/repositories"
	"astrology-api/services"

	"github.com/gin-gonic/gin"
)

// Context keys the admin handlers read.
const (
	// The admin's own user id. 0 when the caller authenticated with the
	// scheduler API key, because no person is behind that call.
	ContextAdminID = "admin_id"

	// JWT or API_KEY. The settlement job records SYSTEM for an API_KEY run,
	// so an automated transition is never mistaken for a human review.
	ContextAdminAuthMode = "admin_auth_mode"

	AdminAuthModeJWT    = "JWT"
	AdminAuthModeAPIKey = "API_KEY"
)

// AdminAuthMiddleware guards the admin API. Two ways in:
//
//   - A logged-in admin's bearer token. The signature is validated and the
//     user must actually hold the admin role — a valid customer token is not
//     enough, which is the point.
//   - X-ADMIN-API-KEY matching ADMIN_API_KEY from the environment. This is
//     for the admin panel's scheduler, which calls the settlement run
//     endpoint with no person logged in. The key is only accepted when
//     ADMIN_API_KEY is actually set, so an empty env var does not turn into
//     an open door.
//
// Unlike JWTAuthMiddleware this does not require the token to still be stored
// on the user row: the admin panel is a separate application with its own
// session handling, and tying it to users.jwt_token would log an admin out of
// the panel every time they logged into the customer app.
func AdminAuthMiddleware() gin.HandlerFunc {

	return func(c *gin.Context) {

		//------------------------------------------------
		// Scheduler Key
		//------------------------------------------------

		configuredKey := strings.TrimSpace(os.Getenv("ADMIN_API_KEY"))

		if providedKey := strings.TrimSpace(c.GetHeader("X-ADMIN-API-KEY")); providedKey != "" {

			if configuredKey == "" || providedKey != configuredKey {

				c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
					"status":  false,
					"message": "Invalid admin API key",
				})
				return
			}

			c.Set(ContextAdminID, uint(0))
			c.Set(ContextAdminAuthMode, AdminAuthModeAPIKey)
			c.Set("user_id", uint(0))

			c.Next()
			return
		}

		//------------------------------------------------
		// Admin Token
		//------------------------------------------------

		authHeader := c.GetHeader("Authorization")

		if authHeader == "" {

			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
				"status":  false,
				"message": "Token required",
			})
			return
		}

		tokenString := strings.TrimPrefix(authHeader, "Bearer ")

		claims, err := services.ValidateJWT(tokenString)

		if err != nil {

			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
				"status":  false,
				"message": "Invalid token",
			})
			return
		}

		idValue, ok := claims["id"]

		if !ok {

			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
				"status":  false,
				"message": "Invalid token payload",
			})
			return
		}

		var adminID uint

		switch value := idValue.(type) {

		case float64:
			adminID = uint(value)

		case int:
			adminID = uint(value)

		case uint:
			adminID = value

		default:

			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
				"status":  false,
				"message": "Invalid token payload",
			})
			return
		}

		//------------------------------------------------
		// Role Check
		//------------------------------------------------

		if !isAdminUser(adminID) {

			c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
				"status":  false,
				"message": "Admin access required",
			})
			return
		}

		c.Set(ContextAdminID, adminID)
		c.Set(ContextAdminAuthMode, AdminAuthModeJWT)
		c.Set("user_id", adminID)

		c.Next()
	}
}

// isAdminUser checks the role mapping table first, because user_roles is the
// one place in this schema whose columns are spelled out explicitly, and
// falls back to the column on the user row.
func isAdminUser(userID uint) bool {

	if userID == 0 {
		return false
	}

	var count int64

	err := configs.DB.
		Model(&usermodel.UserRole{}).
		Where("userId = ?", userID).
		Where("roleId = ?", constants.RoleAdmin).
		Count(&count).Error

	if err == nil && count > 0 {
		return true
	}

	user, err := repositories.FindUserByID(userID)

	if err != nil || user == nil {
		return false
	}

	if user.IsDelete || !user.IsActive {
		return false
	}

	return user.RoleID == uint(constants.RoleAdmin)
}

// GetAdminID reads the admin's user id out of the Gin context. 0 means the
// call came from the scheduler rather than a person.
func GetAdminID(c *gin.Context) uint {

	value, exists := c.Get(ContextAdminID)

	if !exists {
		return 0
	}

	adminID, ok := value.(uint)

	if !ok {
		return 0
	}

	return adminID
}

// IsSystemCaller reports whether the request authenticated with the scheduler
// key, so the settlement trail can record SYSTEM instead of ADMIN.
func IsSystemCaller(c *gin.Context) bool {

	value, exists := c.Get(ContextAdminAuthMode)

	if !exists {
		return false
	}

	mode, ok := value.(string)

	return ok && mode == AdminAuthModeAPIKey
}
