package de.assecutor.votianlt.service;

import de.assecutor.votianlt.model.DeliveryStation;
import de.assecutor.votianlt.model.Job;
import de.assecutor.votianlt.model.task.BaseTask;
import de.assecutor.votianlt.repository.JobRepository;
import de.assecutor.votianlt.repository.TaskRepository;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.bson.types.ObjectId;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class TaskAssignmentService {

    private final TaskRepository taskRepository;
    private final JobRepository jobRepository;

    public List<BaseTask> findTasksForJob(Job job) {
        if (job == null || job.getId() == null) {
            return List.of();
        }

        Map<String, BaseTask> uniqueTasks = new LinkedHashMap<>();
        List<ObjectId> stationIds = extractStationIds(job);

        if (!stationIds.isEmpty()) {
            for (BaseTask task : taskRepository.findByStationIdIn(stationIds)) {
                putIfAbsent(uniqueTasks, task);
            }
        }

        for (BaseTask task : taskRepository.findByJobIdOrderByTaskOrderAsc(job.getId())) {
            putIfAbsent(uniqueTasks, task);
        }

        return sortTasksForJob(job, new ArrayList<>(uniqueTasks.values()));
    }

    public List<BaseTask> findTasksForJob(ObjectId jobId) {
        if (jobId == null) {
            return List.of();
        }

        return jobRepository.findById(jobId).map(this::findTasksForJob)
                .orElseGet(() -> sortTasksForJob(null, taskRepository.findByJobIdOrderByTaskOrderAsc(jobId)));
    }

    public Optional<Job> findJobForTask(BaseTask task) {
        if (task == null) {
            return Optional.empty();
        }

        if (task.getStationId() != null) {
            Optional<Job> jobByStation = jobRepository.findByDeliveryStationsStationId(task.getStationId());
            if (jobByStation.isPresent()) {
                return jobByStation;
            }
        }

        return Optional.ofNullable(task.getJobId()).flatMap(jobRepository::findById);
    }

    private List<ObjectId> extractStationIds(Job job) {
        if (job.getDeliveryStations() == null || job.getDeliveryStations().isEmpty()) {
            return List.of();
        }

        return job.getDeliveryStations().stream().filter(Objects::nonNull).map(DeliveryStation::getStationId)
                .filter(Objects::nonNull).toList();
    }

    private List<BaseTask> sortTasksForJob(Job job, List<BaseTask> tasks) {
        Map<String, Integer> stationOrderById = new LinkedHashMap<>();
        if (job != null && job.getDeliveryStations() != null) {
            for (DeliveryStation station : job.getDeliveryStations()) {
                if (station != null && station.getStationId() != null) {
                    stationOrderById.put(station.getStationId().toHexString(), station.getStationOrder());
                }
            }
        }

        return tasks.stream().filter(Objects::nonNull)
                .sorted(Comparator.<BaseTask> comparingInt(task -> resolveStationOrder(task, stationOrderById))
                        .thenComparingInt(task -> task.getTaskOrder() != null ? task.getTaskOrder() : 0))
                .toList();
    }

    private int resolveStationOrder(BaseTask task, Map<String, Integer> stationOrderById) {
        if (task.getStationId() != null) {
            Integer stationOrder = stationOrderById.get(task.getStationId().toHexString());
            if (stationOrder != null) {
                return stationOrder;
            }
        }
        return task.getStationOrder() != null ? task.getStationOrder() : Integer.MAX_VALUE;
    }

    private void putIfAbsent(Map<String, BaseTask> uniqueTasks, BaseTask task) {
        if (task == null) {
            return;
        }

        String key = task.getId() != null ? task.getId().toHexString()
                : String.join(":", Optional.ofNullable(task.getStationIdAsString()).orElse(""),
                        Optional.ofNullable(task.getJobIdAsString()).orElse(""),
                        String.valueOf(task.getTaskOrder() != null ? task.getTaskOrder() : 0),
                        Optional.ofNullable(task.getDescription()).orElse(""),
                        Optional.ofNullable(task.getTaskType()).orElse(""));
        uniqueTasks.putIfAbsent(key, task);
    }
}
