package rest

import (
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/backend/internal/application/services"
	"github.com/nexuscrm/backend/internal/features/streaming"
	"github.com/nexuscrm/backend/pkg/errors"
	"github.com/rs/zerolog/log"
)

type StreamingHandler struct {
	svcMgr         *services.ServiceManager
	sessionManager *streaming.SessionManager
}

func NewStreamingHandler(svcMgr *services.ServiceManager) *StreamingHandler {
	return &StreamingHandler{
		svcMgr:         svcMgr,
		sessionManager: streaming.DefaultSessionManager(),
	}
}

// CometD Handshake /cometd/:version/handshake
// @Summary CometD handshake endpoint
// @Tags Streaming
// @Accept json
// @Produce json
// @Success 200 {array} object
// @Router /cometd/{version}/handshake [post]
func (h *StreamingHandler) Handshake(c *gin.Context) {
	// Create a new session with unique client ID
	clientID := h.sessionManager.CreateSession()

	log.Debug().
		Str("client_id", clientID).
		Int("active_sessions", h.sessionManager.SessionCount()).
		Msg("CometD handshake - new session created")

	response := []gin.H{
		{
			"channel":                  "/meta/handshake",
			"version":                  "1.0",
			"supportedConnectionTypes": []string{"long-polling", "websocket"},
			"clientId":                 clientID,
			"successful":               true,
		},
	}
	c.JSON(http.StatusOK, response)
}

// CometD Connect /cometd/:version/connect
// @Summary CometD connect endpoint
// @Tags Streaming
// @Accept json
// @Produce json
// @Success 200 {array} object
// @Router /cometd/{version}/connect [post]
func (h *StreamingHandler) Connect(c *gin.Context) {
	var msgs []map[string]interface{}
	if err := c.ShouldBindJSON(&msgs); err != nil {
		// Try to extract clientId from query or fallback
		clientID := c.Query("clientId")
		if clientID == "" {
			RespondAppError(c, errors.NewValidationError("clientId", "Missing client ID"))
			return
		}
	}

	// Extract clientId from messages
	clientID := ""
	if len(msgs) > 0 {
		if id, ok := msgs[0]["clientId"].(string); ok {
			clientID = id
		}
	}

	// Validate session
	if clientID != "" && !h.sessionManager.ValidateSession(clientID) {
		c.JSON(http.StatusOK, []gin.H{
			{
				"channel":    "/meta/connect",
				"successful": false,
				"error":      "Unknown client ID - please handshake first",
				"advice": gin.H{
					"reconnect": "handshake",
				},
			},
		})
		return
	}

	// Wait for messages or timeout (Long Polling)
	session, exists := h.sessionManager.GetSession(clientID)
	if !exists {
		// Session expired while waiting/handling
		c.JSON(http.StatusOK, []gin.H{{
			"channel":    "/meta/connect",
			"successful": false,
			"error":      "Session expired",
		}})
		return
	}

	// Default timeout 30s for long-polling
	timeout := time.After(30 * time.Second)

	// Prepare response
	var responseMessages []map[string]any

	select {
	case msg := <-session.Messages:
		// Received a message!
		// Add the message AND a connect success response
		responseMessages = append(responseMessages, msg)

		// Drain any other immediately available messages (up to limit)
	drain:
		for i := 0; i < 9; i++ { // limit batch size
			select {
			case m := <-session.Messages:
				responseMessages = append(responseMessages, m)
			default:
				break drain
			}
		}

	case <-timeout:
		// Timeout - just send successful connect heartbeat
	case <-c.Request.Context().Done():
		// Client disconnected
		return
	}

	// Always append the connect response at the end
	responseMessages = append(responseMessages, map[string]any{
		"channel":    "/meta/connect",
		"clientId":   clientID,
		"successful": true,
		"advice": map[string]any{
			"reconnect": "retry",
			"interval":  0,
			"timeout":   30000, // 30s timeout matching server wait
		},
	})

	c.JSON(http.StatusOK, responseMessages)
}

// CometD Subscribe /cometd/:version/subscribe
// @Summary CometD subscribe endpoint
// @Tags Streaming
// @Accept json
// @Produce json
// @Success 200 {array} object
// @Router /cometd/{version}/subscribe [post]
func (h *StreamingHandler) Subscribe(c *gin.Context) {
	var msgs []map[string]interface{}
	if err := c.ShouldBindJSON(&msgs); err != nil {
		RespondAppError(c, errors.NewValidationError("body", "Invalid JSON"))
		return
	}

	response := make([]gin.H, len(msgs))
	for i, msg := range msgs {
		clientID, _ := msg["clientId"].(string)
		subscription, _ := msg["subscription"].(string)

		// Validate session
		if !h.sessionManager.ValidateSession(clientID) {
			response[i] = gin.H{
				"channel":      "/meta/subscribe",
				"subscription": subscription,
				"successful":   false,
				"error":        "Unknown client ID",
			}
			continue
		}

		// Add subscription
		h.sessionManager.Subscribe(clientID, subscription)

		log.Debug().
			Str("client_id", clientID).
			Str("subscription", subscription).
			Msg("Client subscribed to channel")

		response[i] = gin.H{
			"channel":      "/meta/subscribe",
			"clientId":     clientID,
			"subscription": subscription,
			"successful":   true,
		}
	}

	c.JSON(http.StatusOK, response)
}

// CometD Disconnect /cometd/:version/disconnect
// @Summary CometD disconnect endpoint
// @Tags Streaming
// @Accept json
// @Produce json
// @Success 200 {array} object
// @Router /cometd/{version}/disconnect [post]
func (h *StreamingHandler) Disconnect(c *gin.Context) {
	var msgs []map[string]interface{}
	if err := c.ShouldBindJSON(&msgs); err != nil {
		log.Debug().Err(err).Msg("Invalid JSON in disconnect parameters")
	}

	clientID := ""
	if len(msgs) > 0 {
		if id, ok := msgs[0]["clientId"].(string); ok {
			clientID = id
		}
	}

	// Destroy session
	if clientID != "" {
		h.sessionManager.DestroySession(clientID)
		log.Debug().
			Str("client_id", clientID).
			Int("remaining_sessions", h.sessionManager.SessionCount()).
			Msg("CometD disconnect - session destroyed")
	}

	response := []gin.H{
		{
			"channel":    "/meta/disconnect",
			"clientId":   clientID,
			"successful": true,
		},
	}
	c.JSON(http.StatusOK, response)
}
