package de.assecutor.votianlt.controller;

import de.assecutor.votianlt.dto.AppLoginRequest;
import de.assecutor.votianlt.dto.AppLoginResponse;
import de.assecutor.votianlt.dto.ChatMessageInboundPayload;
import de.assecutor.votianlt.dto.JobWithRelatedDataDTO;
import de.assecutor.votianlt.model.AppUser;
import de.assecutor.votianlt.model.CargoItem;
import de.assecutor.votianlt.model.Job;
import de.assecutor.votianlt.model.task.BaseTask;
import de.assecutor.votianlt.pages.service.AppUserService;
import de.assecutor.votianlt.repository.AppUserRepository;
import de.assecutor.votianlt.repository.CargoItemRepository;
import de.assecutor.votianlt.repository.JobRepository;
import de.assecutor.votianlt.repository.PhotoRepository;
import de.assecutor.votianlt.repository.TaskRepository;
import de.assecutor.votianlt.repository.BarcodeRepository;
import de.assecutor.votianlt.repository.SignatureRepository;
import de.assecutor.votianlt.repository.CommentRepository;
import de.assecutor.votianlt.model.Photo;
import de.assecutor.votianlt.model.Barcode;
import de.assecutor.votianlt.model.Signature;
import de.assecutor.votianlt.model.Comment;
import de.assecutor.votianlt.service.JobHistoryService;
import de.assecutor.votianlt.service.EmailService;
import de.assecutor.votianlt.service.MessageService;
import de.assecutor.votianlt.service.ClientConnectionService;
import de.assecutor.votianlt.model.JobStatus;
import lombok.extern.slf4j.Slf4j;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.assecutor.votianlt.mqtt.MqttPublisher;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import org.bson.types.ObjectId;

/**
 * MQTT message controller for handling real-time communication with apps.
 * Provides endpoints for sending and receiving messages via WebSocket/MQTT.
 */
@Component
@Slf4j
public class MessageController {

    // Map to store userId -> clientId mapping for active sessions
    private final Map<String, String> userClientIdMapping = new ConcurrentHashMap<>();

    // Map to store clientId -> userId mapping for active sessions (reverse lookup)
    private final Map<String, String> clientIdUserMapping = new ConcurrentHashMap<>();

    private final MqttPublisher mqttPublisher;

    private final AppUserRepository appUserRepository;

    private final AppUserService appUserService;

    private final JobRepository jobRepository;

    private final CargoItemRepository cargoItemRepository;

    private final TaskRepository taskRepository;
    private final PhotoRepository photoRepository;
    private final BarcodeRepository barcodeRepository;
    private final SignatureRepository signatureRepository;
    private final CommentRepository commentRepository;
    private final JobHistoryService jobHistoryService;
    private final EmailService emailService;
    private final MessageService messageService;
    private final ObjectMapper objectMapper;
    private final ClientConnectionService clientConnectionService;

    public MessageController(MqttPublisher mqttPublisher, AppUserRepository appUserRepository,
            AppUserService appUserService, JobRepository jobRepository, CargoItemRepository cargoItemRepository,
            TaskRepository taskRepository, PhotoRepository photoRepository, BarcodeRepository barcodeRepository,
            SignatureRepository signatureRepository, CommentRepository commentRepository, JobHistoryService jobHistoryService,
            EmailService emailService, MessageService messageService, ObjectMapper objectMapper,
            ClientConnectionService clientConnectionService) {
        this.mqttPublisher = mqttPublisher;
        this.appUserRepository = appUserRepository;
        this.appUserService = appUserService;
        this.jobRepository = jobRepository;
        this.cargoItemRepository = cargoItemRepository;
        this.taskRepository = taskRepository;
        this.photoRepository = photoRepository;
        this.barcodeRepository = barcodeRepository;
        this.signatureRepository = signatureRepository;
        this.commentRepository = commentRepository;
        this.jobHistoryService = jobHistoryService;
        this.emailService = emailService;
        this.messageService = messageService;
        this.objectMapper = objectMapper;
        this.clientConnectionService = clientConnectionService;
    }

    /**
     * Authentication endpoint for mobile app users via MQTT. Client sends to
     * /server/login with payload { email, password, clientId }. The response is
     * sent back to the requesting client on /client/{clientId}/auth
     */
    public void handleAppLogin(AppLoginRequest request) {
        log.info("MQTT Endpoint '/server/login' called with email: {}, clientId: {}",
                request != null ? request.getEmail() : "null", request != null ? request.getClientId() : "null");

        AppLoginResponse response;

        if (request == null || request.getEmail() == null || request.getPassword() == null
                || request.getClientId() == null || request.getEmail().isBlank() || request.getPassword().isBlank()
                || request.getClientId().isBlank()) {
            response = new AppLoginResponse(false, "E-Mail, Passwort und Client-ID sind erforderlich", null, null,
                    null);
        } else {
            AppUser user = appUserRepository.findByEmail(request.getEmail());
            if (user == null) {
                response = new AppLoginResponse(false, "Benutzer nicht gefunden", null, null, null);
            } else {
                boolean ok = appUserService.verifyPassword(request.getPassword(), user.getPassword());
                if (!ok) {
                    response = new AppLoginResponse(false, "Ungültige Anmeldedaten", null, null, null);
                } else {
                    response = new AppLoginResponse(true, "Anmeldung erfolgreich", null, null, user.getIdAsString());
                    // Store clientId mapping for this user session
                    storeClientIdMapping(user.getIdAsString(), request.getClientId());
                    // Register client for ping/pong monitoring
                    clientConnectionService.registerClient(request.getClientId(), user.getIdAsString());
                }
            }
        }

        // Send response via MQTT to specific client
        if (request != null && request.getClientId() != null && !request.getClientId().isBlank()) {
            mqttPublisher.publishAsJson("/client/" + request.getClientId() + "/auth", response, false);
            log.info("MQTT Response sent to '/client/{}/auth': success={}, message='{}'", request.getClientId(),
                    response.isSuccess(), response.getMessage());
        }
    }

    /**
     * Endpoint to retrieve jobs assigned to a specific app user with related cargo
     * items and tasks. Client sends to /server/{clientId}/jobs/assigned with
     * payload { appUserId }. The response is sent back to the requesting client on
     * /client/{clientId}/jobs
     */
    public void handleGetAssignedJobs(Map<String, Object> request) {
        log.info("MQTT Endpoint '/server/{clientId}/jobs/assigned' called with data: {}", request);
        log.debug("Starting to process jobs request for MQTT endpoint");

        if (request == null || !request.containsKey("appUserId")) {
            log.info("Assigned jobs request missing appUserId; returning empty list");
            return; // Return empty list if no appUserId provided
        }

        String appUserId = request.get("appUserId").toString();
        if (appUserId == null || appUserId.isBlank()) {
            log.info("Assigned jobs request blank appUserId; returning empty list");
            return; // Return empty list if appUserId is blank
        }

        // Attempt to get clientId from request (injected from topic) or from stored
        // mapping
        String clientId = null;
        try {
            Object cid = request.get("clientId");
            if (cid != null)
                clientId = cid.toString();
        } catch (Exception e) {
            log.debug("Could not extract clientId from request: {}", e.getMessage());
        }
        if (clientId == null || clientId.isBlank()) {
            clientId = getClientIdForUserId(appUserId);
        }

        // Find jobs assigned to this app user
        List<Job> assignedJobs = jobRepository.findByAppUser(appUserId);
        log.info("Found {} jobs for appUserId: {}", assignedJobs.size(), appUserId);

        // Debug: Log all jobs and their app_user values to diagnose assignment issues
        List<Job> allJobs = jobRepository.findAll();
        log.info("DEBUG: Total jobs in database: {}", allJobs.size());
        for (Job job : allJobs) {
            log.info("DEBUG: Job {} (number: {}) has app_user='{}', digitalProcessing={}",
                job.getIdAsString(), job.getJobNumber(), job.getAppUser(), job.isDigitalProcessing());
        }

        // For each job, fetch related cargo items and tasks (ordered by task order)
        List<JobWithRelatedDataDTO> jobsWithRelatedData = assignedJobs.stream().map(job -> {
            List<CargoItem> cargoItems = cargoItemRepository.findByJobId(job.getId());
            List<BaseTask> tasks = taskRepository.findByJobIdOrderByTaskOrderAsc(job.getId());

            // Log task details for debugging
            tasks.forEach(task -> log.info("Task details for job {}: type={}, order={}", job.getId(),
                    task.getTaskType(), task.getTaskOrder()));

            return new JobWithRelatedDataDTO(job, cargoItems, tasks);
        }).toList();

        // Publish to the requesting client's topic if clientId is known
        if (clientId != null && !clientId.isBlank()) {
            String topic = "/client/" + clientId + "/jobs";
            mqttPublisher.publishAsJson(topic, jobsWithRelatedData, false);
            log.info("Published {} assigned jobs for appUserId='{}' to topic '{}'", jobsWithRelatedData.size(),
                    appUserId, topic);
        } else {
            log.warn("No clientId available to publish assigned jobs for appUserId='{}'. Skipping MQTT publish.",
                    appUserId);
        }

        // Log complete JSON for debugging
        log.debug("About to serialize {} jobs to JSON for logging", jobsWithRelatedData.size());
        try {
            String jsonOutput = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jobsWithRelatedData);
            log.info("=== COMPLETE JSON RESPONSE FOR MQTT CLIENT ===");
            log.info("AppUserId: {}", appUserId);
            log.info("ClientId: {}", clientId);
            log.info("Number of jobs: {}", jobsWithRelatedData.size());
            log.info("JSON Data:\n{}", jsonOutput);
            log.info("=== END JSON RESPONSE ===");
        } catch (Exception e) {
            log.error("Failed to serialize jobs to JSON for logging: {}", e.getMessage(), e);
        }

    }

    /**
     * Report generic task completion from apps. Client sends to /app/task/completed
     * with payload { taskId, completedBy?, note? }. Broadcasts to
     * /topic/task-updates and /topic/tasks/{taskId}. This endpoint accepts any task
     * type (fallback for GENERIC or unknown types).
     */
    public void handleTaskCompleted(Map<String, Object> payload) {
        // Backward-compatible entry point: extract taskType from payload (if present)
        // and delegate to the overloaded handler with explicit type.
        String taskType = null;
        try {
            Object tt = payload != null ? payload.get("taskType") : null;
            if (tt != null)
                taskType = tt.toString();
        } catch (Exception e) {
            log.debug("Could not extract taskType from payload: {}", e.getMessage());
        }
        handleTaskCompleted(payload, taskType);
    }

    /**
     * Central dispatcher for task_completed messages. Decides handling based on
     * taskType. PHOTO and CONFIRMATION are routed to specialized handlers; others
     * go to generic processing.
     */
    public void handleTaskCompleted(Map<String, Object> payload, String taskType) {
        String key = taskType == null ? "" : taskType.trim().toUpperCase();

        log.info("handleTaskCompleted called with taskType={}, data: {}", taskType, payload);

        switch (key) {
        case "PHOTO" -> {
            processPhotoTaskCompletion(payload);
        }
        case "CONFIRMATION" -> {
            processConfirmationTaskCompletion(payload);
        }
        case "SIGNATURE" -> {
            processSignatureTaskCompletion(payload);
        }
        case "TODOLIST" -> {
            processTodoListTaskCompletion(payload);
        }
        case "BARCODE" -> {
            processBarcodeTaskCompletion(payload);
        }
        case "COMMENT" -> {
            processCommentTaskCompletion(payload);
        }
        default -> {
            log.info("ERROR: handleTaskCompleted called with taskType={}, data: {}", taskType, payload);
        }
        }
    }

    private void processConfirmationTaskCompletion(Map<String, Object> payload) {
        Object taskId = payload.get("taskId");
        completeTaskWithHistory(taskId, "Bestätigung durchgeführt");
    }

    private void processTodoListTaskCompletion(Map<String, Object> payload) {
        Object taskId = payload.get("taskId");
        completeTaskWithHistory(taskId, "Alle To-Do-Elemente abgehakt");
    }

    private void processBarcodeTaskCompletion(Map<String, Object> payload) {
        Object taskId = payload.get("taskId");
        try {
            var opt = taskRepository.findById(new ObjectId(taskId.toString()));
            if (opt.isEmpty()) {
                log.warn("Task not found for barcode completion. taskId={}", taskId);
                return;
            }
            BaseTask task = opt.get();

            String extraDataSummary = null;
            Object extra = payload.get("extraData");
            if (extra instanceof Map<?, ?> extraData) {
                Object barcodesObj = extraData.get("barcodes");
                if (barcodesObj instanceof List<?> barcodesList) {
                    // Suppressing unchecked cast warning as extraData structure is validated from MQTT payload
                    @SuppressWarnings("unchecked")
                    List<String> barcodes = (List<String>) barcodesList;

                    if (!barcodes.isEmpty()) {
                        for (String barcodeString : barcodes) {
                            String completedBy = task.getCompletedBy() != null ? task.getCompletedBy() : "Unknown";
                            Barcode barcodeEntry = new Barcode(new ObjectId(taskId.toString()), barcodeString,
                                    completedBy);

                            barcodeRepository.save(barcodeEntry);
                        }

                        extraDataSummary = barcodes.size() + " Barcode(s) gescannt: "
                                + String.join(", ", barcodes.subList(0, Math.min(3, barcodes.size())))
                                + (barcodes.size() > 3 ? "..." : "");
                        log.info("Saved {} barcodes for taskId={}", barcodes.size(), taskId);
                    } else {
                        extraDataSummary = "Keine Barcodes gescannt";
                        log.info("No barcodes found in extraData for taskId={}", taskId);
                    }
                } else {
                    extraDataSummary = "Barcode-Daten fehlerhaft";
                    log.warn("extraData.barcodes is not a List for taskId={}", taskId);
                }
            } else {
                extraDataSummary = "Keine Extra-Daten";
                log.warn("extraData is not a Map for taskId={}", taskId);
            }

            // Finally, mark the task as completed with history logging
            completeTaskWithHistory(taskId, extraDataSummary);
        } catch (IllegalArgumentException ex) {
            log.error("Invalid taskId format for barcode completion: {}", taskId);
        } catch (Exception ex) {
            log.error("Error while processing barcode task completion (taskId={}): {}", taskId, ex.getMessage(), ex);
        }
    }

    private void processSignatureTaskCompletion(Map<String, Object> payload) {
        Object taskId = payload.get("taskId");
        try {
            var opt = taskRepository.findById(new ObjectId(taskId.toString()));
            if (opt.isEmpty()) {
                log.warn("Task not found for signature completion. taskId={}", taskId);
                return;
            }
            BaseTask task = opt.get();

            String extraDataSummary = null;
            Object extra = payload.get("extraData");
            if (extra instanceof Map<?, ?> extraData) {
                Object signatureSvgObj = extraData.get("signatureSvg");
                if (signatureSvgObj instanceof String signatureSvg) {
                    if (!signatureSvg.isBlank()) {
                        String completedBy = task.getCompletedBy() != null ? task.getCompletedBy() : "Unknown";
                        Signature signatureEntry = new Signature(new ObjectId(taskId.toString()), signatureSvg,
                                completedBy);

                        signatureRepository.save(signatureEntry);
                        extraDataSummary = "Unterschrift erfasst (SVG, " + signatureSvg.length() + " Zeichen)";
                        log.info("Saved signature for taskId={}", taskId);
                    } else {
                        extraDataSummary = "Leere Unterschrift";
                        log.info("Empty signature SVG found for taskId={}", taskId);
                    }
                } else {
                    extraDataSummary = "Unterschrift-Daten fehlerhaft";
                    log.warn("extraData.signatureSvg is not a String for taskId={}", taskId);
                }
            } else {
                extraDataSummary = "Keine Extra-Daten";
                log.warn("extraData is not a Map for taskId={}", taskId);
            }

            // Finally, mark the task as completed with history logging
            completeTaskWithHistory(taskId, extraDataSummary);
        } catch (IllegalArgumentException ex) {
            log.error("Invalid taskId format for signature completion: {}", taskId);
        } catch (Exception ex) {
            log.error("Error while processing signature task completion (taskId={}): {}", taskId, ex.getMessage(), ex);
        }
    }

    private void processPhotoTaskCompletion(Map<String, Object> payload) {
        Object taskId = payload.get("taskId");
        try {
            var opt = taskRepository.findById(new ObjectId(taskId.toString()));
            if (opt.isEmpty()) {
                log.warn("Task not found for photo completion. taskId={}", taskId);
                return;
            }
            BaseTask task = opt.get();
            ObjectId jobId = new ObjectId(task.getJobIdAsString());

            String extraDataSummary = null;
            Object extra = payload.get("extraData");
            if (extra instanceof Map<?, ?> extraData) {
                Object photosObj = extraData.get("photos");
                if (photosObj instanceof List<?> photosList) {
                    // Suppressing unchecked cast warning as extraData structure is validated from MQTT payload
                    @SuppressWarnings("unchecked")
                    List<String> photos = (List<String>) photosList;

                    if (!photos.isEmpty()) {
                        for (String photoString : photos) {
                            String completedBy = task.getCompletedBy() != null ? task.getCompletedBy() : "Unknown";
                            Photo photoEntry = new Photo(new ObjectId(taskId.toString()), photoString,
                                    completedBy);

                            photoRepository.save(photoEntry);
                        }

                        extraDataSummary = photos.size() + " Foto(s) aufgenommen";
                        log.info("Saved {} photos for taskId={}, jobId={}", photos.size(), taskId, jobId);
                    } else {
                        extraDataSummary = "Keine Fotos aufgenommen";
                        log.info("No photos found in extraData for taskId={}", taskId);
                    }
                } else {
                    extraDataSummary = "Foto-Daten fehlerhaft";
                    log.warn("extraData.photos is not a List for taskId={}", taskId);
                }
            } else {
                extraDataSummary = "Keine Extra-Daten";
                log.warn("extraData is not a Map for taskId={}", taskId);
            }

            // Finally, mark the task as completed with history logging
            completeTaskWithHistory(taskId, extraDataSummary);
        } catch (IllegalArgumentException ex) {
            log.error("Invalid taskId format for photo completion: {}", taskId);
        } catch (Exception ex) {
            log.error("Error while processing photo task completion (taskId={}): {}", taskId, ex.getMessage(), ex);
        }
    }

    private void processCommentTaskCompletion(Map<String, Object> payload) {
        Object taskId = payload.get("taskId");
        try {
            var opt = taskRepository.findById(new ObjectId(taskId.toString()));
            if (opt.isEmpty()) {
                log.warn("Task not found for comment completion. taskId={}", taskId);
                return;
            }
            BaseTask task = opt.get();

            String extraDataSummary = null;
            Object extra = payload.get("extraData");
            if (extra instanceof Map<?, ?> extraData) {
                Object commentTextObj = extraData.get("commentText");
                if (commentTextObj instanceof String commentText && !commentText.isBlank()) {
                    // Save comment to database
                    String completedBy = task.getCompletedBy() != null ? task.getCompletedBy() : "Unknown";
                    Comment commentEntry = new Comment(new ObjectId(taskId.toString()), commentText, completedBy);
                    commentRepository.save(commentEntry);

                    extraDataSummary = "Kommentar: " + commentText;
                    log.info("Comment saved for taskId={}: {}", taskId, commentText);
                } else {
                    extraDataSummary = "Kommentar abgegeben (leer)";
                }
            } else {
                extraDataSummary = "Kommentar abgegeben";
                log.warn("extraData is not a Map for comment task completion, taskId={}", taskId);
            }

            // Finally, mark the task as completed with history logging
            completeTaskWithHistory(taskId, extraDataSummary);
        } catch (IllegalArgumentException ex) {
            log.error("Invalid taskId format for comment completion: {}", taskId);
        } catch (Exception ex) {
            log.error("Error while processing comment task completion (taskId={}): {}", taskId, ex.getMessage(), ex);
        }
    }

    private void completeTaskWithHistory(Object tid, String extraDataSummary) {
        String taskIdStr = tid.toString();
        try {
            ObjectId taskId = new ObjectId(taskIdStr);
            var opt = taskRepository.findById(taskId);
            if (opt.isEmpty()) {
                log.warn("Task not found for completion. taskId={}", taskIdStr);
                return;
            }
            BaseTask task = opt.get();
            task.setCompleted(true);
            task.setCompletedAt(LocalDateTime.now());
            taskRepository.save(task);

            // Log detailed task completion in job history
            try {
                ObjectId jobId = new ObjectId(task.getJobIdAsString());
                String taskType = task.getTaskType() != null ? task.getTaskType().toString() : "Unknown";
                String taskDisplayName = task.getDisplayName() != null ? task.getDisplayName() : taskType;

                String completedBy = task.getCompletedBy() != null ? task.getCompletedBy() : "Unknown";
                jobHistoryService.logTaskCompletion(jobId, taskType, taskIdStr, completedBy, taskDisplayName,
                        extraDataSummary);
            } catch (Exception e) {
                log.warn("Failed to log task completion history for task {}: {}", taskIdStr, e.getMessage());
            }

            // Send email notification for task completion
            try {
                ObjectId jobId = new ObjectId(task.getJobIdAsString());
                String taskType = task.getTaskType() != null ? task.getTaskType().toString() : "Unknown";
                String completedBy = task.getCompletedBy() != null ? task.getCompletedBy() : "Unknown";

                emailService.sendTaskCompletionNotification(jobId, taskType, taskIdStr, completedBy);

                // Check if all tasks are completed and handle job completion
                checkAndHandleJobCompletion(jobId, completedBy);
            } catch (Exception e) {
                log.warn("Failed to send task completion email notification for task {}: {}", taskIdStr,
                        e.getMessage());
            }

            String completedBy = task.getCompletedBy() != null ? task.getCompletedBy() : "Unknown";
            log.info("Task marked completed. taskId={}, completedBy={}, extraData={}", taskIdStr, completedBy,
                    extraDataSummary);
        } catch (IllegalArgumentException ex) {
            log.error("Invalid taskId format for completion: {}", taskIdStr);
        } catch (Exception ex) {
            log.error("Error while marking task completed (taskId={}): {}", taskIdStr, ex.getMessage(), ex);
        }
    }

    private void checkAndHandleJobCompletion(ObjectId jobId, String completedBy) {
        try {
            // Check if all tasks for this job are completed
            var allTasks = taskRepository.findByJobIdOrderByTaskOrderAsc(jobId);
            if (allTasks.isEmpty()) {
                log.debug("No tasks found for job {}", jobId);
                return;
            }

            boolean allCompleted = allTasks.stream().allMatch(task -> task.isCompleted());

            if (allCompleted) {
                log.info("All tasks completed for job {}, updating job status and sending completion notification",
                        jobId);

                // Update job status to COMPLETED
                updateJobStatusToCompleted(jobId);

                // Send completion notification
                try {
                    emailService.sendJobCompletionNotification(jobId, completedBy);
                } catch (Exception e) {
                    log.warn("Failed to send job completion notification for job {}: {}", jobId, e.getMessage());
                }
            } else {
                long completedCount = allTasks.stream().mapToLong(task -> task.isCompleted() ? 1L : 0L).sum();
                log.debug("Job {} not yet complete: {}/{} tasks completed", jobId, completedCount, allTasks.size());
            }
        } catch (Exception e) {
            log.error("Failed to check job completion for job {}: {}", jobId, e.getMessage(), e);
        }
    }

    private void updateJobStatusToCompleted(ObjectId jobId) {
        try {
            Optional<Job> jobOpt = jobRepository.findById(jobId);
            if (jobOpt.isEmpty()) {
                log.warn("Job not found for status update: {}", jobId);
                return;
            }

            Job job = jobOpt.get();
            JobStatus oldStatus = job.getStatus();

            // Only update if not already completed
            if (job.getStatus() != JobStatus.COMPLETED) {
                job.setStatus(JobStatus.COMPLETED);
                job.setUpdatedAt(LocalDateTime.now());
                jobRepository.save(job);

                log.info("Job status updated from {} to COMPLETED for job {}", oldStatus != null ? oldStatus : "null",
                        job.getJobNumber());
            } else {
                log.debug("Job {} already has COMPLETED status", job.getJobNumber());
            }
        } catch (Exception e) {
            log.error("Failed to update job status to COMPLETED for job {}: {}", jobId, e.getMessage(), e);
        }
    }

    /**
     * Store the mapping between userId and clientId for active session
     */
    private void storeClientIdMapping(String userId, String clientId) {
        userClientIdMapping.put(userId, clientId);
        clientIdUserMapping.put(clientId, userId);
        log.debug("Stored clientId mapping: userId={} <-> clientId={}", userId, clientId);
    }

    /**
     * Get the clientId for a given userId
     */
    private String getClientIdForUserId(String userId) {
        return userClientIdMapping.get(userId);
    }

    /**
     * Handle pong response from a client.
     * Client sends to /server/{clientId}/pong with payload { timestamp }.
     * Used for connection monitoring.
     */
    public void handlePong(Map<String, Object> payload) {
        String clientId = payload.get("clientId") != null ? payload.get("clientId").toString() : null;
        log.debug("Received pong from client: {}", clientId);

        if (clientId != null && !clientId.isBlank()) {
            clientConnectionService.handlePong(clientId);
        } else {
            log.warn("Received pong without clientId in payload");
        }
    }

    /**
     * Handle incoming message from a client via MQTT.
     * Client sends to /server/{clientId}/message with payload:
     * {
     *   "content": "message payload",
     *   "contentType": "TEXT|IMAGE",
     *   "jobId": "optional job id",
     *   "jobNumber": "optional job number"
     * }
     *
     * The clientId is extracted from the MQTT topic and represents the AppUser ID.
     * This clientId is stored as the receiver field in the message.
     */
    public void handleIncomingMessage(Map<String, Object> payload) {
        log.info("MQTT Endpoint '/server/{clientId}/message' called with data: {}", payload);

        try {
            // Extract clientId from payload (added by MqttV5ClientManager from topic)
            // The clientId IS the AppUser ID
            String clientId = payload.get("clientId") != null ? payload.get("clientId").toString() : null;

            if (clientId == null || clientId.isBlank()) {
                log.warn("No clientId found in message payload, cannot process message");
                return;
            }

            // Add clientId as receiver to the payload
            payload.put("receiver", clientId);

            // Parse the payload
            ChatMessageInboundPayload inboundPayload = ChatMessageInboundPayload.fromPayload(payload);

            // Save the message with receiver = AppUser ID (clientId)
            messageService.receiveMessageFromClient(inboundPayload);
            log.info("Successfully saved incoming message for AppUser '{}'", clientId);
        } catch (IllegalArgumentException validationError) {
            log.warn("Incoming chat message rejected: {}", validationError.getMessage());
        } catch (Exception e) {
            log.error("Error handling incoming message: {}", e.getMessage(), e);
        }
    }

}
