package dev.lions.services;

import dev.lions.dtos.NotificationDTO;
import dev.lions.exceptions.WebSocketException;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;

import lombok.extern.slf4j.Slf4j;

import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Service gérant les communications WebSocket de l'application.
 * Cette classe assure la gestion des connexions temps réel et la diffusion
 * des notifications aux clients connectés.
 */
@Slf4j
@ApplicationScoped
@ServerEndpoint("/ws/notifications")
public class WebSocketService {

  private static final Map<String, Session> sessions = new ConcurrentHashMap<>();
  private static final int MAX_MESSAGE_SIZE = 8192;
  private static final long IDLE_TIMEOUT = 300000; // 5 minutes

  /**
   * Gère l'ouverture d'une nouvelle connexion WebSocket.
   *
   * @param session Session WebSocket ouverte
   */
  @OnOpen
  public void onOpen(Session session) {
    log.info("Nouvelle connexion WebSocket établie : {}", session.getId());

    try {
      configureSession(session);
      sessions.put(session.getId(), session);

      sendWelcomeMessage(session);

    } catch (Exception e) {
      log.error("Erreur lors de l'initialisation de la session WebSocket", e);
      closeSession(session, "Erreur d'initialisation");
    }
  }

  /**
   * Configure une nouvelle session WebSocket.
   */
  private void configureSession(Session session) {
    session.setMaxIdleTimeout(IDLE_TIMEOUT);
    session.setMaxTextMessageBufferSize(MAX_MESSAGE_SIZE);
    session.setMaxBinaryMessageBufferSize(MAX_MESSAGE_SIZE);
  }

  /**
   * Envoie un message de bienvenue au client connecté.
   */
  private void sendWelcomeMessage(Session session) {
    try {
      String message = "{\"type\":\"welcome\",\"message\":\"Connexion établie\"}";
      session.getBasicRemote().sendText(message);
    } catch (Exception e) {
      log.warn("Impossible d'envoyer le message de bienvenue", e);
    }
  }

  /**
   * Gère la réception d'un message depuis un client.
   *
   * @param message Message reçu
   * @param session Session WebSocket active
   */
  @OnMessage
  public void onMessage(String message, Session session) {
    String sessionId = session.getId();
    log.debug("Message reçu de la session {} : {}", sessionId, message);

    try {
      validateMessage(message);
      processMessage(message, session);
    } catch (Exception e) {
      log.error("Erreur lors du traitement du message", e);
      sendErrorResponse(session, "Erreur de traitement du message");
    }
  }

  /**
   * Valide le contenu d'un message reçu.
   */
  private void validateMessage(String message) {
    if (message == null || message.trim().isEmpty()) {
      throw new WebSocketException("Message vide non autorisé");
    }
    if (message.length() > MAX_MESSAGE_SIZE) {
      throw new WebSocketException("Message trop long");
    }
  }

  /**
   * Traite un message reçu.
   */
  private void processMessage(String message, Session session) {
    // Implémentation du traitement des messages selon les besoins
    log.debug("Traitement du message de la session {}", session.getId());
  }

  /**
   * Gère la fermeture d'une connexion WebSocket.
   *
   * @param session Session WebSocket fermée
   */
  @OnClose
  public void onClose(Session session) {
    String sessionId = session.getId();
    log.info("Fermeture de la connexion WebSocket : {}", sessionId);

    sessions.remove(sessionId);
    cleanupSession(session);
  }

  /**
   * Gère les erreurs survenant sur une connexion WebSocket.
   *
   * @param session Session WebSocket concernée
   * @param throwable Erreur survenue
   */
  @OnError
  public void onError(Session session, Throwable throwable) {
    String sessionId = session.getId();
    log.error("Erreur WebSocket sur la session {} : {}",
              sessionId, throwable.getMessage(), throwable);

    closeSession(session, "Erreur interne");
  }

  /**
   * Diffuse une notification à tous les clients connectés.
   *
   * @param notification Notification à diffuser
   */
  public void broadcastToAdmins(NotificationDTO notification) {
    log.debug("Diffusion d'une notification à {} clients", sessions.size());

    String message = notification.toJson();
    sessions.values().forEach(session -> {
      try {
        if (session.isOpen()) {
          session.getBasicRemote().sendText(message);
        }
      } catch (Exception e) {
        log.warn("Erreur lors de l'envoi à la session {}",
                 session.getId(), e);
      }
    });
  }

  /**
   * Envoie une réponse d'erreur à un client.
   */
  private void sendErrorResponse(Session session, String errorMessage) {
    try {
      String message = String.format(
          "{\"type\":\"error\",\"message\":\"%s\"}",
          errorMessage
      );
      session.getBasicRemote().sendText(message);
    } catch (Exception e) {
      log.error("Impossible d'envoyer la réponse d'erreur", e);
    }
  }

  /**
   * Ferme une session WebSocket.
   */
  private void closeSession(Session session, String reason) {
    try {
      session.close(new CloseReason(
          CloseReason.CloseCodes.NORMAL_CLOSURE,
          reason
      ));
    } catch (Exception e) {
      log.warn("Erreur lors de la fermeture de la session", e);
    }
  }

  /**
   * Nettoie les ressources d'une session.
   */
  private void cleanupSession(Session session) {
    try {
      session.close();
    } catch (Exception e) {
      log.warn("Erreur lors du nettoyage de la session", e);
    }
  }

  /**
   * Récupère le nombre de clients connectés.
   */
  public int getConnectedClientsCount() {
    return sessions.size();
  }
}