package middleware

import (
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"

	"chat-switchboard/auth"
	"chat-switchboard/config"
	"chat-switchboard/database"
	"chat-switchboard/store"
)

// AuthOrSession returns middleware that accepts either a normal JWT or a
// session cookie. Authenticated users get the standard context values
// (user_id, email, role, auth_type="user"). Session visitors get
// session_id, channel_id, and auth_type="session".
//
// Session auth is only valid for workflow channels with allow_anonymous=true.
func AuthOrSession(cfg *config.Config, stores store.Stores, cache *UserStatusCache) gin.HandlerFunc {
	return func(c *gin.Context) {
		// Skip auth when running without a database
		if !database.IsConnected() {
			c.Next()
			return
		}

		// ── Try normal JWT auth first ──
		tokenString := extractBearerToken(c)
		if tokenString != "" {
			if claims, ok := parseAndValidateJWT(tokenString, cfg.JWTSecret); ok {
				role, valid := verifyUser(c, claims, stores.Users, cache)
				if !valid {
					return
				}
				c.Set("user_id", claims.UserID)
				c.Set("email", claims.Email)
				c.Set("role", role)
				c.Set("auth_type", "user")
				c.Next()
				return
			}
		}

		// ── Try sb_token cookie (page auth pattern) ──
		if cookie, err := c.Cookie("sb_token"); err == nil && cookie != "" {
			if claims, ok := parseAndValidateJWT(cookie, cfg.JWTSecret); ok {
				role, valid := verifyUser(c, claims, stores.Users, cache)
				if !valid {
					return
				}
				c.Set("user_id", claims.UserID)
				c.Set("email", claims.Email)
				c.Set("role", role)
				c.Set("auth_type", "user")
				c.Next()
				return
			}
		}

		// ── Fall back to session auth ──
		channelID := c.Param("id") // channel routes use :id
		if channelID == "" {
			channelID = c.Param("channelId")
		}
		if channelID == "" {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "authentication required"})
			return
		}

		// Verify channel exists, is workflow type, and allows anonymous
		chType, allowAnon, err := stores.Channels.GetTypeAndAllowAnonymous(c.Request.Context(), channelID)
		if err != nil {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "authentication required"})
			return
		}
		if chType != "workflow" || !allowAnon {
			c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "anonymous access not permitted on this channel"})
			return
		}

		session, err := auth.CreateOrResumeSession(c, stores, channelID, cfg)
		if err != nil {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "session creation failed"})
			return
		}

		c.Set("session_id", session.ID)
		c.Set("session_token", session.SessionToken)
		c.Set("channel_id", channelID)
		c.Set("auth_type", "session")
		c.Next()
	}
}

// extractBearerToken gets the JWT from Authorization header or ?token= query param.
func extractBearerToken(c *gin.Context) string {
	header := c.GetHeader("Authorization")
	if header == "" {
		if qToken := c.Query("token"); qToken != "" {
			return qToken
		}
		return ""
	}
	if strings.HasPrefix(header, "Bearer ") {
		return strings.TrimPrefix(header, "Bearer ")
	}
	return ""
}
