package rest

import (
	"encoding/json"
	"net/http"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"github.com/nexuscrm/backend/internal/application/services"
	"github.com/nexuscrm/backend/internal/features/streaming"
	origincfg "github.com/nexuscrm/backend/internal/interfaces/origin"
	"github.com/nexuscrm/backend/pkg/flags"
	"github.com/rs/zerolog/log"
)

// wsClient wraps a WebSocket connection with write mutex
type wsClient struct {
	conn    *websocket.Conn
	writeMu sync.Mutex
}

func (c *wsClient) WriteJSON(v interface{}) error {
	c.writeMu.Lock()
	defer c.writeMu.Unlock()
	return c.conn.WriteJSON(v)
}

func (c *wsClient) WriteMessage(messageType int, data []byte) error {
	c.writeMu.Lock()
	defer c.writeMu.Unlock()
	return c.conn.WriteMessage(messageType, data)
}

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin:     checkWebSocketOrigin,
}

// checkWebSocketOrigin validates the origin for WebSocket connections
func checkWebSocketOrigin(r *http.Request) bool {
	origin := r.Header.Get("Origin")
	if origin == "" {
		return true // Allow requests without Origin header (e.g., curl)
	}

	if origincfg.IsAllowedOrigin(origin) {
		return true
	}

	log.Warn().Str("origin", origin).Msg("WebSocket connection rejected: origin not allowed")
	return false
}

// WebSocketHandler handles WebSocket connections for real-time streaming
type WebSocketHandler struct {
	svcMgr         *services.ServiceManager
	sessionManager *streaming.SessionManager
	clients        map[string]*wsClient
	mu             sync.RWMutex
}

// NewWebSocketHandler creates a new WebSocket handler
func NewWebSocketHandler(svcMgr *services.ServiceManager, sessionMgr *streaming.SessionManager) *WebSocketHandler {
	return &WebSocketHandler{
		svcMgr:         svcMgr,
		sessionManager: sessionMgr,
		clients:        make(map[string]*wsClient),
	}
}

// HandleConnection upgrades HTTP to WebSocket and manages the connection
// @Summary WebSocket connection endpoint
// @Tags Streaming
// @Success 101 {string} string "Switching Protocols"
// @Router /ws [get]
func (h *WebSocketHandler) HandleConnection(c *gin.Context) {
	if flags.IsDisabled(flags.FeatureWebSocket) {
		log.Warn().Msg("WebSocket feature is disabled")
		c.Status(http.StatusServiceUnavailable)
		return
	}

	conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		log.Error().Err(err).Msg("WebSocket upgrade failed")
		return
	}
	defer conn.Close()

	// Create session for this WebSocket connection
	clientID := h.sessionManager.CreateSession()
	client := &wsClient{conn: conn}

	h.mu.Lock()
	h.clients[clientID] = client
	h.mu.Unlock()

	defer func() {
		h.mu.Lock()
		delete(h.clients, clientID)
		h.mu.Unlock()
		h.sessionManager.DestroySession(clientID)
	}()

	log.Info().
		Str("client_id", clientID).
		Str("remote_addr", c.ClientIP()).
		Msg("WebSocket client connected")

	// Send handshake response
	handshakeMsg := map[string]interface{}{
		"channel":    "/meta/handshake",
		"clientId":   clientID,
		"successful": true,
		"version":    "1.0",
	}
	if err := client.WriteJSON(handshakeMsg); err != nil {
		log.Error().Err(err).Msg("Failed to send handshake")
		return
	}

	// Set read deadline for ping/pong
	conn.SetReadDeadline(time.Now().Add(120 * time.Second))
	conn.SetPongHandler(func(string) error {
		conn.SetReadDeadline(time.Now().Add(120 * time.Second))
		return nil
	})

	// Start ping ticker
	ticker := time.NewTicker(30 * time.Second)
	defer ticker.Stop()

	done := make(chan struct{})

	// Handle incoming messages
	go func() {
		defer close(done)
		for {
			_, message, err := conn.ReadMessage()
			if err != nil {
				if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
					log.Warn().Err(err).Str("client_id", clientID).Msg("WebSocket read error")
				}
				return
			}

			h.handleMessage(clientID, message)
		}
	}()

	// Send pings
	for {
		select {
		case <-done:
			return
		case <-ticker.C:
			if err := client.WriteMessage(websocket.PingMessage, nil); err != nil {
				return
			}
		}
	}
}

// handleMessage processes incoming WebSocket messages
func (h *WebSocketHandler) handleMessage(clientID string, message []byte) {
	var msg map[string]interface{}
	if err := json.Unmarshal(message, &msg); err != nil {
		log.Warn().Err(err).Msg("Invalid WebSocket message format")
		return
	}

	channel, _ := msg["channel"].(string)

	switch channel {
	case "/meta/subscribe":
		subscription, _ := msg["subscription"].(string)
		h.sessionManager.Subscribe(clientID, subscription)
		h.sendToClient(clientID, map[string]interface{}{
			"channel":      "/meta/subscribe",
			"subscription": subscription,
			"successful":   true,
		})
		log.Debug().Str("client_id", clientID).Str("channel", subscription).Msg("WebSocket subscription added")

	case "/meta/unsubscribe":
		subscription, _ := msg["subscription"].(string)
		h.sessionManager.Unsubscribe(clientID, subscription)
		h.sendToClient(clientID, map[string]interface{}{
			"channel":      "/meta/unsubscribe",
			"subscription": subscription,
			"successful":   true,
		})

	case "/meta/disconnect":
		// Client will be cleaned up when connection closes
		h.sendToClient(clientID, map[string]interface{}{
			"channel":    "/meta/disconnect",
			"successful": true,
		})
	}
}

// sendToClient sends a message to a specific client
func (h *WebSocketHandler) sendToClient(clientID string, msg interface{}) error {
	h.mu.RLock()
	client, exists := h.clients[clientID]
	h.mu.RUnlock()

	if !exists {
		return nil
	}

	return client.WriteJSON(msg)
}

// Broadcast sends a message to all clients subscribed to a channel
func (h *WebSocketHandler) Broadcast(channel string, data interface{}) {
	h.mu.RLock()
	clientsCopy := make(map[string]*wsClient, len(h.clients))
	for id, c := range h.clients {
		clientsCopy[id] = c
	}
	h.mu.RUnlock()

	message := map[string]interface{}{
		"channel": channel,
		"data":    data,
	}

	for clientID, client := range clientsCopy {
		// Check if client is subscribed to this channel
		channels := h.sessionManager.GetSubscribedChannels(clientID)
		for _, ch := range channels {
			if ch == channel {
				if err := client.WriteJSON(message); err != nil {
					log.Warn().Err(err).Str("client_id", clientID).Msg("Failed to broadcast to client")
				}
				break
			}
		}
	}
}

// ClientCount returns the number of connected WebSocket clients
func (h *WebSocketHandler) ClientCount() int {
	h.mu.RLock()
	defer h.mu.RUnlock()
	return len(h.clients)
}
