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.model.task.SignatureTask;
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.JobUpdateBroadcaster;
import de.assecutor.votianlt.service.EmailService;
import de.assecutor.votianlt.service.MessageService;
import de.assecutor.votianlt.service.TaskAssignmentService;
import de.assecutor.votianlt.model.JobStatus;
import lombok.extern.slf4j.Slf4j;
import de.assecutor.votianlt.messaging.MessagingPublisher;
import org.springframework.stereotype.Component;

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

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

    private final MessagingPublisher messagingPublisher;

    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 JobUpdateBroadcaster jobUpdateBroadcaster;
    private final EmailService emailService;
    private final MessageService messageService;
    private final TaskAssignmentService taskAssignmentService;

    public MessageController(MessagingPublisher messagingPublisher, AppUserRepository appUserRepository,
            AppUserService appUserService, JobRepository jobRepository, CargoItemRepository cargoItemRepository,
            TaskRepository taskRepository, PhotoRepository photoRepository, BarcodeRepository barcodeRepository,
            SignatureRepository signatureRepository, CommentRepository commentRepository,
            JobHistoryService jobHistoryService, JobUpdateBroadcaster jobUpdateBroadcaster, EmailService emailService,
            MessageService messageService, TaskAssignmentService taskAssignmentService) {
        this.messagingPublisher = messagingPublisher;
        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.jobUpdateBroadcaster = jobUpdateBroadcaster;
        this.emailService = emailService;
        this.messageService = messageService;
        this.taskAssignmentService = taskAssignmentService;
    }

    /**
     * Authentication endpoint for mobile app users via WebSocket. Client sends to
     * /server/login with payload { email, password }. Returns the result to the
     * caller (MessagingConfig) which handles session registration and response
     * sending.
     */
    public AppLoginResponse handleAppLogin(AppLoginRequest request) {
        if (request == null || request.getEmail() == null || request.getPassword() == null
                || request.getEmail().isBlank() || request.getPassword().isBlank()) {
            return new AppLoginResponse(false, "E-Mail und Passwort sind erforderlich", null);
        }

        AppUser user = appUserRepository.findByEmail(request.getEmail());
        if (user == null) {
            return new AppLoginResponse(false, "Benutzer nicht gefunden", null);
        }

        boolean ok = appUserService.verifyPassword(request.getPassword(), user.getPassword());
        if (!ok) {
            return new AppLoginResponse(false, "Ungültige Anmeldedaten", null);
        }

        return new AppLoginResponse(true, "Anmeldung erfolgreich", user.getIdAsString());
    }

    /**
     * Retrieve jobs assigned to a specific app user with related cargo items and
     * tasks. The appUserId is determined from the authenticated WebSocket session.
     * Response is sent back on /client/jobs.
     */
    public void handleGetAssignedJobs(String appUserId) {
        if (appUserId == null || appUserId.isBlank()) {
            log.warn("[JOBS] appUserId is null or blank, cannot retrieve jobs");
            return;
        }

        log.info("[JOBS] Retrieving assigned jobs for appUserId: {}", appUserId);

        List<Job> assignedJobs = jobRepository.findByAppUser(appUserId).stream()
                .filter(job -> job.getStatus() != JobStatus.COMPLETED && job.getStatus() != JobStatus.CANCELLED)
                .toList();
        log.info("[JOBS] Found {} open jobs for appUserId: {}", assignedJobs.size(), appUserId);

        List<JobWithRelatedDataDTO> jobsWithRelatedData = assignedJobs.stream().map(job -> {
            List<CargoItem> cargoItems = cargoItemRepository.findByJobId(job.getId());
            List<BaseTask> tasks = taskAssignmentService.findTasksForJob(job);
            for (BaseTask task : tasks) {
                if (task instanceof SignatureTask signatureTask && task.getId() != null) {
                    List<Signature> signatures = signatureRepository.findByTaskIdOrderByCreatedAtDesc(task.getId());
                    if (!signatures.isEmpty()) {
                        signatureTask.setNote(signatures.get(0).getNote());
                    }
                }
            }
            return new JobWithRelatedDataDTO(job, cargoItems, tasks);
        }).toList();

        log.info("[JOBS] Publishing {} jobs to client {} on topic /client/jobs", jobsWithRelatedData.size(), appUserId);
        messagingPublisher.publishAsJson(appUserId, "jobs", jobsWithRelatedData);
        log.info("[JOBS] Jobs published successfully for client {}", appUserId);
    }

    /**
     * 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();

        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.error("[TASK] Unknown taskType: {}", taskType);
        }
    }

    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()) {
                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) {
                    @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 ? "..." : "");
                    } else {
                        extraDataSummary = "Keine Barcodes gescannt";
                    }
                } else {
                    extraDataSummary = "Barcode-Daten fehlerhaft";
                }
            } else {
                extraDataSummary = "Keine Extra-Daten";
            }

            completeTaskWithHistory(taskId, extraDataSummary);
        } catch (Exception ex) {
            log.error("[TASK] Barcode completion error: {}", ex.getMessage());
        }
    }

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

            String extraDataSummary = null;
            Object extra = payload.get("extraData");
            if (extra instanceof Map<?, ?> extraData) {
                Object signatureSvgObj = extraData.get("signatureSvg");
                Object signatureNoteObj = extraData.get("signatureNote");
                String signatureNote = signatureNoteObj instanceof String s ? s : null;
                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,
                                signatureNote, completedBy);
                        signatureRepository.save(signatureEntry);
                        extraDataSummary = "Unterschrift erfasst (SVG, " + signatureSvg.length() + " Zeichen)";
                        if (signatureNote != null && !signatureNote.isBlank()) {
                            extraDataSummary += ", Bemerkung: " + signatureNote;
                        }
                    } else {
                        extraDataSummary = "Leere Unterschrift";
                    }
                } else {
                    extraDataSummary = "Unterschrift-Daten fehlerhaft";
                }
            } else {
                extraDataSummary = "Keine Extra-Daten";
            }

            completeTaskWithHistory(taskId, extraDataSummary);
        } catch (Exception ex) {
            log.error("[TASK] Signature completion error: {}", ex.getMessage());
        }
    }

    private void processPhotoTaskCompletion(Map<String, Object> payload) {
        Object taskId = payload.get("taskId");
        try {
            var opt = taskRepository.findById(new ObjectId(taskId.toString()));
            if (opt.isEmpty()) {
                return;
            }
            BaseTask task = opt.get();

            String extraDataSummary = null;
            Object extra = payload.get("extraData");
            if (extra instanceof Map<?, ?> extraData) {
                Object photosObj = extraData.get("photos");
                if (photosObj instanceof List<?> photosList) {
                    @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";
                    } else {
                        extraDataSummary = "Keine Fotos aufgenommen";
                    }
                } else {
                    extraDataSummary = "Foto-Daten fehlerhaft";
                }
            } else {
                extraDataSummary = "Keine Extra-Daten";
            }

            completeTaskWithHistory(taskId, extraDataSummary);
        } catch (Exception ex) {
            log.error("[TASK] Photo completion error: {}", ex.getMessage());
        }
    }

    private void processCommentTaskCompletion(Map<String, Object> payload) {
        Object taskId = payload.get("taskId");
        try {
            var opt = taskRepository.findById(new ObjectId(taskId.toString()));
            if (opt.isEmpty()) {
                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()) {
                    String completedBy = task.getCompletedBy() != null ? task.getCompletedBy() : "Unknown";
                    Comment commentEntry = new Comment(new ObjectId(taskId.toString()), commentText, completedBy);
                    commentRepository.save(commentEntry);
                    extraDataSummary = "Kommentar: " + commentText;
                } else {
                    extraDataSummary = "Kommentar abgegeben (leer)";
                }
            } else {
                extraDataSummary = "Kommentar abgegeben";
            }

            completeTaskWithHistory(taskId, extraDataSummary);
        } catch (Exception ex) {
            log.error("[TASK] Comment completion error: {}", ex.getMessage());
        }
    }

    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()) {
                return;
            }
            BaseTask task = opt.get();
            task.setCompleted(true);
            task.setCompletedAt(LocalDateTime.now());
            taskRepository.save(task);
            Optional<Job> jobOpt = taskAssignmentService.findJobForTask(task);
            if (jobOpt.isEmpty()) {
                log.warn("[TASK] Could not resolve job for task {}", taskIdStr);
                return;
            }
            ObjectId jobId = jobOpt.get().getId();

            // Log detailed task completion in job history
            try {
                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.error("[TASK] Could not write job history for task {} of job {}: {}", taskIdStr, jobId,
                        e.getMessage(), e);
            }

            // Send email notification for task completion
            try {
                String taskType = task.getTaskType() != null ? task.getTaskType().toString() : "Unknown";
                String completedBy = task.getCompletedBy() != null ? task.getCompletedBy() : "Unknown";
                emailService.sendTaskCompletionNotification(jobId, taskType, taskIdStr, completedBy);
                // Job completion is no longer auto-triggered by task completion.
                // It is now driven by explicit station_completed messages from the app
                // (see handleStationCompleted).
            } catch (Exception e) {
                log.error("[TASK] Could not send completion notification for task {} of job {}: {}", taskIdStr, jobId,
                        e.getMessage(), e);
            }

            jobUpdateBroadcaster.broadcast(jobId);
        } catch (Exception ex) {
            log.error("[TASK] Completion error: {}", ex.getMessage());
        }
    }

    private void checkAndHandleJobCompletion(ObjectId jobId, String completedBy) {
        try {
            var allTasks = taskAssignmentService.findTasksForJob(jobId);
            if (allTasks.isEmpty()) {
                return;
            }

            var mandatoryTasks = allTasks.stream().filter(task -> !task.isOptional()).toList();
            if (mandatoryTasks.isEmpty()) {
                return;
            }
            boolean allCompleted = mandatoryTasks.stream().allMatch(task -> task.isCompleted());

            if (allCompleted) {
                updateJobStatusToCompleted(jobId);
                try {
                    emailService.sendJobCompletionNotification(jobId, completedBy);
                } catch (Exception e) {
                    log.error("[JOB] Could not send job completion notification for job {}: {}", jobId, e.getMessage(),
                            e);
                }
            } else {
                List<String> openTaskIds = mandatoryTasks.stream().filter(task -> !task.isCompleted())
                        .map(task -> task.getIdAsString()).toList();
                log.info("[JOB] Job {} not completed yet, {} of {} mandatory tasks still open: {}", jobId,
                        openTaskIds.size(), mandatoryTasks.size(), openTaskIds);
            }
        } catch (Exception e) {
            log.error("[JOB] Completion check failed for job {}: {}", jobId, e.getMessage(), e);
        }
    }

    private void updateJobStatusToCompleted(ObjectId jobId) {
        try {
            Optional<Job> jobOpt = jobRepository.findById(jobId);
            if (jobOpt.isEmpty()) {
                log.warn("[JOB] Cannot mark job {} as completed: job not found", jobId);
                return;
            }

            Job job = jobOpt.get();
            if (job.getStatus() != JobStatus.COMPLETED) {
                job.setStatus(JobStatus.COMPLETED);
                job.setUpdatedAt(LocalDateTime.now());
                jobRepository.save(job);
                log.info("[JOB] Job {} marked as COMPLETED", jobId);
            }
        } catch (Exception e) {
            log.error("[JOB] Could not update status of job {} to COMPLETED: {}", jobId, e.getMessage(), e);
        }
    }

    /**
     * Handle station completion message from app. Client sends to
     * /server/station_completed with payload:
     * {
     *   "jobId": "jobnum:ABC123",
     *   "jobNumber": "ABC123",
     *   "stationOrder": 0,
     *   "completedAt": "2026-04-13T12:34:56.789Z",
     *   "hasIncompleteOptionalTasks": false
     * }
     *
     * The job is marked as completed once this message is received and all
     * mandatory tasks across all stations are completed.
     */
    public void handleStationCompleted(String appUserId, Map<String, Object> payload) {
        try {
            String jobIdStr = payload.get("jobId") != null ? payload.get("jobId").toString() : null;
            String jobNumber = payload.get("jobNumber") != null ? payload.get("jobNumber").toString() : null;

            Optional<Job> jobOpt = Optional.empty();
            if (jobIdStr != null && ObjectId.isValid(jobIdStr)) {
                jobOpt = jobRepository.findById(new ObjectId(jobIdStr));
            }
            if (jobOpt.isEmpty() && jobNumber != null && !jobNumber.isBlank()) {
                jobOpt = jobRepository.findByJobNumber(jobNumber);
            }
            if (jobOpt.isEmpty()) {
                log.warn("[STATION] Job not found for station_completed (jobId={}, jobNumber={})", jobIdStr,
                        jobNumber);
                return;
            }

            ObjectId jobId = jobOpt.get().getId();
            String completedBy = appUserId != null ? appUserId : "Unknown";

            log.info("[STATION] station_completed received for jobNumber={}, stationOrder={}", jobNumber,
                    payload.get("stationOrder"));

            checkAndHandleJobCompletion(jobId, completedBy);
            jobUpdateBroadcaster.broadcast(jobId);
        } catch (Exception e) {
            log.error("[STATION] Error handling station_completed: {}", e.getMessage());
        }
    }

    /**
     * Handle incoming message from a client via WebSocket. Client sends to
     * /server/message with payload: { "content": "message payload", "contentType":
     * "TEXT|IMAGE", "jobId": "optional job id", "jobNumber": "optional job number"
     * }
     *
     * The appUserId is determined from the authenticated WebSocket session.
     */
    public void handleIncomingMessage(String appUserId, Map<String, Object> payload) {
        try {
            if (appUserId == null || appUserId.isBlank()) {
                return;
            }

            payload.put("receiver", appUserId);
            ChatMessageInboundPayload inboundPayload = ChatMessageInboundPayload.fromPayload(payload);
            messageService.receiveMessageFromClient(inboundPayload);
        } catch (Exception e) {
            log.error("[MESSAGE] Could not process incoming message from app user {}: {}", appUserId, e.getMessage(),
                    e);
        }
    }

}
