package auth

import (
	"net/http"
	"strings"

	"bookra/apps/backend/internal/db"
	"bookra/apps/backend/internal/domain"

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

const principalContextKey = "principal"

// DemoPrincipal is the auto-authenticated user in demo mode
var DemoPrincipal = domain.Principal{
	Subject: "demo-owner",
	Email:   "demo@bookra.dev",
	Name:    "Demo User",
	Role:    "owner",
}

func RequireAuth(verifier *Verifier, repo db.Repository, demoMode bool) gin.HandlerFunc {
	return func(c *gin.Context) {
		// In demo mode, auto-authenticate as the demo user
		if demoMode {
			c.Set(principalContextKey, DemoPrincipal)
			c.Next()
			return
		}

		if verifier == nil || !verifier.Enabled() {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "auth_not_configured"})
			return
		}

		header := c.GetHeader("Authorization")
		tokenString, ok := strings.CutPrefix(header, "Bearer ")
		if !ok || strings.TrimSpace(tokenString) == "" {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing_bearer_token"})
			return
		}

		claims, err := verifier.Verify(strings.TrimSpace(tokenString))
		if err != nil {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid_token"})
			return
		}

		subject, _ := claims["sub"].(string)
		email, _ := claims["email"].(string)
		name, _ := claims["name"].(string)
		if name == "" {
			name, _ = claims["display_name"].(string)
		}
		role, _ := claims["role"].(string)
		if role == "" {
			role = "authenticated"
		}
		if strings.TrimSpace(subject) == "" {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid_token_subject"})
			return
		}
		if repo != nil {
			if err := repo.EnsureUserIdentity(c.Request.Context(), subject, email, name); err != nil {
				c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "identity_sync_failed"})
				return
			}
		}

		c.Set(principalContextKey, domain.Principal{
			Subject: subject,
			Email:   email,
			Name:    name,
			Role:    role,
		})

		c.Next()
	}
}

func PrincipalFromContext(c *gin.Context) domain.Principal {
	value, ok := c.Get(principalContextKey)
	if !ok {
		return domain.Principal{}
	}
	principal, _ := value.(domain.Principal)
	return principal
}
