package de.assecutor.votianlt.pages.service;

import de.assecutor.votianlt.dto.JobWithRelatedDataDTO;
import de.assecutor.votianlt.model.CargoItem;
import de.assecutor.votianlt.model.Job;
import de.assecutor.votianlt.model.JobStatus;
import de.assecutor.votianlt.model.task.BaseTask;
import de.assecutor.votianlt.messaging.MessagingPublisher;
import de.assecutor.votianlt.repository.JobRepository;
import de.assecutor.votianlt.repository.TaskRepository;
import de.assecutor.votianlt.security.SecurityService;
import de.assecutor.votianlt.repository.CargoItemRepository;
import de.assecutor.votianlt.service.ClientConnectionService;
import de.assecutor.votianlt.service.JobHistoryService;
import de.assecutor.votianlt.service.EmailService;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Optional;

@Service
@RequiredArgsConstructor
@Slf4j
public class AddJobService {

    private final CargoItemRepository cargoItemRepository;
    private final JobRepository jobRepository;
    private final TaskRepository taskRepository;
    private final SecurityService securityService;
    private final JobHistoryService jobHistoryService;
    private final EmailService emailService;
    private final ClientConnectionService clientConnectionService;
    private final MessagingPublisher messagingPublisher;

    /**
     * Speichert einen neuen Auftrag samt CargoItems und Tasks
     * 
     * @param job
     *            der Auftrag
     * @param transientCargo
     *            zugehörige, noch nicht gespeicherte CargoItems aus der View
     */
    public Job addJobWithCargo(Job job, List<CargoItem> transientCargo, List<BaseTask> transientTasks) {
        try {
            // Metadaten setzen
            LocalDateTime now = LocalDateTime.now();
            job.setCreatedAt(now);
            job.setUpdatedAt(now);
            job.setStatus(JobStatus.CREATED);
            job.setCreatedBy(securityService.getCurrentUserId().toHexString());

            // Auftragsnummer generieren, falls nicht vorhanden
            if (job.getJobNumber() == null || job.getJobNumber().isEmpty()) {
                job.setJobNumber(generateJobNumber());
            }

            // Auftrag speichern
            Job savedJob = jobRepository.save(job);
            final ObjectId jobId = savedJob.getId();
            boolean modified = false;

            // CargoItems separat mit Referenz auf Job speichern, IDs im Job verknüpfen
            if (transientCargo != null && !transientCargo.isEmpty()) {
                List<CargoItem> itemsWithJob = transientCargo.stream().filter(Objects::nonNull)
                        .filter(ci -> ci.getDescription() != null && !ci.getDescription().isBlank()).map(ci -> {
                            CargoItem copy = new CargoItem();
                            copy.setJobId(jobId);
                            copy.setDescription(ci.getDescription());
                            copy.setQuantity(ci.getQuantity());
                            copy.setWeightKg(ci.getWeightKg());
                            copy.setLengthMm(ci.getLengthMm());
                            copy.setWidthMm(ci.getWidthMm());
                            copy.setHeightMm(ci.getHeightMm());
                            return copy;
                        }).toList();
                cargoItemRepository.saveAll(itemsWithJob);
                modified = true;
            }

            // Tasks separat speichern und referenzieren mit korrekter Nummerierung
            if (transientTasks != null && !transientTasks.isEmpty()) {
                var filteredTasks = transientTasks.stream().filter(Objects::nonNull)
                        .filter(task -> task.getTaskType() != null) // Filter nach TaskType statt Text
                        .toList();

                // Setze JobId und stelle sicher, dass taskOrder korrekt ist
                for (int i = 0; i < filteredTasks.size(); i++) {
                    BaseTask task = filteredTasks.get(i);
                    task.setJobId(jobId);
                    // Verwende die bereits gesetzte taskOrder oder setze sie auf den Index
                    if (task.getTaskOrder() == null || task.getTaskOrder() != i) {
                        task.setTaskOrder(i); // Stelle sicher, dass die Reihenfolge stimmt
                    }
                }

                taskRepository.saveAll(filteredTasks);
                log.info("Saved {} tasks for job {} with ordering", filteredTasks.size(), jobId);
            }

            if (modified) {
                savedJob = jobRepository.save(savedJob);
            }

            // History-Eintrag für Job-Erstellung
            try {
                String currentUserName = getCurrentUserName();
                jobHistoryService.logJobCreation(savedJob, currentUserName);
            } catch (Exception e) {
                log.warn("Failed to log job creation history for job {}: {}", savedJob.getIdAsString(), e.getMessage());
            }

            // E-Mail-Benachrichtigung für Job-Erstellung senden
            try {
                emailService.sendJobCreationNotification(savedJob.getId(), savedJob.getCreatedBy());
            } catch (Exception e) {
                log.warn("Failed to send job creation email notification for job {}: {}", savedJob.getIdAsString(),
                        e.getMessage());
            }

            // Benachrichtigung an Client senden, wenn online
            notifyClientJobCreated(savedJob);

            log.info("Auftrag erfolgreich gespeichert: {}", savedJob.getJobNumber());
            return savedJob;

        } catch (Exception e) {
            log.error("Fehler beim Speichern des Auftrags: {}", e.getMessage(), e);
            throw new RuntimeException("Auftrag konnte nicht gespeichert werden: " + e.getMessage());
        }
    }

    /**
     * Generiert eine eindeutige Auftragsnummer
     */
    private String generateJobNumber() {
        String prefix = "JOB";
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));

        // Zähle Aufträge des aktuellen Tages
        String todayPrefix = prefix + timestamp;
        long todayCount = jobRepository.findAll().stream()
                .filter(job -> job.getJobNumber() != null && job.getJobNumber().startsWith(todayPrefix)).count();

        // Generiere neue Nummer
        String jobNumber;
        do {
            todayCount++;
            jobNumber = String.format("%s%s%03d", prefix, timestamp, todayCount);
        } while (jobRepository.existsByJobNumber(jobNumber));

        return jobNumber;
    }

    /**
     * Findet den aktuellen Entwurf eines Benutzers
     */
    public Optional<Job> findCurrentDraft(String username) {
        List<Job> drafts = jobRepository.findByCreatedByAndIsDraftTrue(username);
        return drafts.isEmpty() ? Optional.empty() : Optional.of(drafts.getFirst());
    }

    /**
     * Hilfsmethode um den aktuellen Benutzernamen zu ermitteln
     */
    private String getCurrentUserName() {
        try {
            var authenticatedUserOpt = securityService.getAuthenticatedUser();
            if (authenticatedUserOpt.isPresent()) {
                var user = authenticatedUserOpt.get();
                return user.getUsername() != null ? user.getUsername() : "Unknown User";
            }
        } catch (Exception e) {
            log.debug("Could not get authenticated user: {}", e.getMessage());
        }
        return "System"; // Fallback wenn kein authentifizierter Benutzer gefunden wird
    }

    /**
     * Sendet den neu erstellten Job per WebSocket an den zugewiesenen Client, falls
     * dieser online ist.
     */
    private void notifyClientJobCreated(Job job) {
        if (!job.isDigitalProcessing()) {
            return;
        }

        String appUserId = job.getAppUser();
        if (appUserId == null || appUserId.isBlank()) {
            return;
        }

        if (!clientConnectionService.isClientConnected(appUserId)) {
            log.info("[JOB] Client {} not online, skipping job_created notification", appUserId);
            return;
        }

        try {
            // Lade CargoItems und Tasks für den Job
            List<CargoItem> cargoItems = cargoItemRepository.findByJobId(job.getId());
            List<BaseTask> tasks = taskRepository.findByJobIdOrderByTaskOrderAsc(job.getId());

            // Erstelle DTO mit allen Daten
            JobWithRelatedDataDTO jobData = new JobWithRelatedDataDTO(job, cargoItems, tasks);

            log.info("[JOB] Sending job_created to {}: jobId={}, jobNumber={}", appUserId, job.getId().toHexString(),
                    job.getJobNumber());
            messagingPublisher.publishAsJson(appUserId, "job_created", jobData);
        } catch (Exception e) {
            log.warn("[JOB] Failed to send job_created notification: {}", e.getMessage());
        }
    }
}
