package com.aps.service.Algorithm;

import com.aps.entity.Algorithm.Chromosome;
import com.aps.entity.Algorithm.GAScheduleResult;
import com.aps.entity.Algorithm.OrderMaterialRequirement;
import com.aps.entity.basic.Entry;
import com.aps.entity.basic.GlobalParam;
import com.aps.entity.basic.Material;
import com.aps.entity.Stock;

import java.time.LocalDateTime;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 缂栬緫鍚庣殑缁熶竴 JIT 鍚庡鐞嗭細
 * 鍏堜繚璇佸彲琛岋紝鍐嶆妸鍙楀奖鍝嶇殑鍗婃垚鍝佸皬鑼冨洿寰€鍚庢媺锛屽敖閲忚创杩戞柊鐨勭墿鏂欓渶姹傚伐搴忓紑宸ユ椂闂淬€?
 */
public class PostEditJitService {

    public boolean applyPostEditJitBias(Chromosome chromosome, GlobalParam globalParam) {
        clearTemporaryJitBias(chromosome);
        if (chromosome == null
                || chromosome.getAllOperations() == null
                || chromosome.getAllOperations().isEmpty()
                || chromosome.getResult() == null
                || chromosome.getResult().isEmpty()) {
            return false;
        }

        Map<Integer, GAScheduleResult> currentResults = chromosome.getResult().stream()
                .collect(Collectors.toMap(GAScheduleResult::getOperationId, r -> r, (left, right) -> right));
        Map<Integer, GAScheduleResult> previousResults = chromosome.getResultOld() == null
                ? Collections.emptyMap()
                : chromosome.getResultOld().stream()
                .collect(Collectors.toMap(GAScheduleResult::getOperationId, r -> r, (left, right) -> right));
        Map<Integer, List<Entry>> operationsByGroup = chromosome.getAllOperations().stream()
                .collect(Collectors.groupingBy(Entry::getGroupId));
        Map<String, Material> materialsById = chromosome.getMaterials() == null
                ? Collections.emptyMap()
                : chromosome.getMaterials();
        Map<Integer, Set<Integer>> childGroupsByParentOp = buildChildGroupIndex(chromosome.getAllOperations());

        if (childGroupsByParentOp.isEmpty()) {
            return false;
        }

        int bufferSeconds = Math.max(0, globalParam.getSemiJitBufferMinutes()) * 60;
        Map<Integer, Integer> pendingParentStarts = new HashMap<>();
        ArrayDeque<Integer> queue = new ArrayDeque<>();

        for (Map.Entry<Integer, GAScheduleResult> entry : currentResults.entrySet()) {
            int operationId = entry.getKey();
            if (!isOperationChanged(entry.getValue(), previousResults.get(operationId))) {
                continue;
            }
            if (!childGroupsByParentOp.containsKey(operationId) || childGroupsByParentOp.get(operationId).isEmpty()) {
                continue;
            }
            pendingParentStarts.put(operationId, entry.getValue().getStartTime());
            queue.offer(operationId);
        }

        if (queue.isEmpty()) {
            return false;
        }

        boolean updated = false;
        while (!queue.isEmpty()) {
            Integer parentOperationId = queue.poll();
            Integer parentStartTime = pendingParentStarts.get(parentOperationId);
            if (parentStartTime == null) {
                continue;
            }
            Set<Integer> childGroupIds = childGroupsByParentOp.get(parentOperationId);
            if (childGroupIds == null || childGroupIds.isEmpty()) {
                continue;
            }

            for (Integer childGroupId : childGroupIds) {
                List<Entry> childOperations = operationsByGroup.get(childGroupId);
                if (childOperations == null || childOperations.isEmpty()) {
                    continue;
                }

                int currentGroupStart = Integer.MAX_VALUE;
                int currentGroupEnd = Integer.MIN_VALUE;
                boolean allScheduled = true;
                for (Entry childOperation : childOperations) {
                    GAScheduleResult childResult = currentResults.get(childOperation.getId());
                    if (childResult == null) {
                        allScheduled = false;
                        break;
                    }
                    currentGroupStart = Math.min(currentGroupStart, childResult.getStartTime());
                    currentGroupEnd = Math.max(currentGroupEnd, childResult.getEndTime());
                }
                if (!allScheduled || currentGroupEnd < currentGroupStart) {
                    continue;
                }

                Entry firstChildOperation = childOperations.stream()
                        .min(Comparator.comparingInt(Entry::getSequence))
                        .orElse(null);
                if (firstChildOperation == null) {
                    continue;
                }

                GAScheduleResult firstChildResult = currentResults.get(firstChildOperation.getId());
                if (firstChildResult != null
                        && (firstChildResult.isIsLocked() || firstChildResult.getLockStartTime() == 1)) {
                    continue;
                }

                int groupSpan = currentGroupEnd - currentGroupStart;
                int preferredStartTime = Math.max(0, parentStartTime - bufferSeconds - groupSpan);
                int effectivePreferredStart = preferredStartTime;
                Integer latestStockBackedStart = resolveLatestStockBackedGroupStart(
                        childOperations, currentResults, materialsById, chromosome.getBaseTime(), currentGroupStart);
                if (latestStockBackedStart != null) {
                    effectivePreferredStart = Math.min(effectivePreferredStart, latestStockBackedStart);
                }

                if (firstChildOperation.isJitTemporary() && firstChildOperation.getJitPreferredStartTime() != null) {
                    int existingPreferredStart = (int) ChronoUnit.SECONDS.between(
                            chromosome.getBaseTime(), firstChildOperation.getJitPreferredStartTime());
                    effectivePreferredStart = Math.min(existingPreferredStart, preferredStartTime);
                    if (latestStockBackedStart != null) {
                        effectivePreferredStart = Math.min(effectivePreferredStart, latestStockBackedStart);
                    }
                }

                if (effectivePreferredStart <= currentGroupStart) {
                    continue;
                }

                firstChildOperation.setJitPreferredStartTime(chromosome.getBaseTime().plusSeconds(effectivePreferredStart));
                firstChildOperation.setJitTemporary(true);
                updated = true;

                int groupShift = effectivePreferredStart - currentGroupStart;
                if (groupShift <= 0) {
                    continue;
                }

                for (Entry childOperation : childOperations) {
                    if (!childGroupsByParentOp.containsKey(childOperation.getId())
                            || childGroupsByParentOp.get(childOperation.getId()).isEmpty()) {
                        continue;
                    }
                    GAScheduleResult childResult = currentResults.get(childOperation.getId());
                    if (childResult == null) {
                        continue;
                    }
                    int shiftedParentStart = childResult.getStartTime() + groupShift;
                    Integer existingTarget = pendingParentStarts.get(childOperation.getId());
                    if (existingTarget == null || shiftedParentStart > existingTarget) {
                        pendingParentStarts.put(childOperation.getId(), shiftedParentStart);
                        queue.offer(childOperation.getId());
                    }
                }
            }
        }

        return updated;
    }

    public void clearTemporaryJitBias(Chromosome chromosome) {
        if (chromosome == null || chromosome.getAllOperations() == null) {
            return;
        }
        for (Entry operation : chromosome.getAllOperations()) {
            operation.setJitPreferredStartTime(null);
            operation.setJitTemporary(false);
        }
    }

    private boolean isOperationChanged(GAScheduleResult current, GAScheduleResult previous) {
        if (current == null) {
            return false;
        }
        if (previous == null) {
            return true;
        }
        return current.getStartTime() != previous.getStartTime()
                || current.getEndTime() != previous.getEndTime()
                || current.getMachineId() != previous.getMachineId();
    }

    private Integer resolveLatestStockBackedGroupStart(List<Entry> childOperations,
                                                       Map<Integer, GAScheduleResult> currentResults,
                                                       Map<String, Material> materialsById,
                                                       LocalDateTime baseTime,
                                                       int currentGroupStart) {
        Integer latestGroupStart = null;
        for (Entry childOperation : childOperations) {
            GAScheduleResult childResult = currentResults.get(childOperation.getId());
            if (childResult == null || childOperation.getMaterialRequirements() == null || childOperation.getMaterialRequirements().isEmpty()) {
                continue;
            }
            int opOffset = childResult.getStartTime() - currentGroupStart;
            for (OrderMaterialRequirement requirement : childOperation.getMaterialRequirements()) {
                if (requirement == null || requirement.getUseStock() <= 0 || !"MP".equals(requirement.getMaterialTypeName())) {
                    continue;
                }
                Material material = materialsById.get(requirement.getMaterialId());
                Integer latestOpStart = resolveLatestStockBackedOperationStart(material, requirement.getUseStock(), baseTime);
                if (latestOpStart == null) {
                    continue;
                }
                int candidateGroupStart = latestOpStart - opOffset;
                latestGroupStart = latestGroupStart == null
                        ? candidateGroupStart
                        : Math.min(latestGroupStart, candidateGroupStart);
            }
        }
        return latestGroupStart;
    }

    private Integer resolveLatestStockBackedOperationStart(Material material, double useStockQty, LocalDateTime baseTime) {
        if (material == null || material.getMaterialStocks() == null || material.getMaterialStocks().isEmpty() || useStockQty <= 0) {
            return null;
        }

        double remaining = useStockQty;
        LocalDateTime earliestUsedExpiry = null;
        List<Stock> stocks = material.getMaterialStocks().stream()
                .filter(stock -> stock != null && stock.getTotal() - stock.getTotalLock() > 0)
                .sorted(Comparator.comparing(Stock::getExpiryDate, Comparator.nullsLast(LocalDateTime::compareTo))
                        .thenComparing(Stock::getWarehousingDate, Comparator.nullsLast(LocalDateTime::compareTo)))
                .collect(Collectors.toList());

        for (Stock stock : stocks) {
            double usableQty = stock.getTotal() - stock.getTotalLock();
            if (usableQty <= 0) {
                continue;
            }
            double consumeQty = Math.min(remaining, usableQty);
            if (consumeQty <= 0) {
                continue;
            }
            if (stock.getExpiryDate() != null) {
                earliestUsedExpiry = earliestUsedExpiry == null || stock.getExpiryDate().isBefore(earliestUsedExpiry)
                        ? stock.getExpiryDate()
                        : earliestUsedExpiry;
            }
            remaining -= consumeQty;
            if (remaining <= 0) {
                break;
            }
        }

        if (earliestUsedExpiry == null) {
            return null;
        }
        return (int) ChronoUnit.SECONDS.between(baseTime, earliestUsedExpiry);
    }

    private Map<Integer, Set<Integer>> buildChildGroupIndex(Collection<Entry> operations) {
        Map<Integer, Set<Integer>> childGroupsByParentOp = new HashMap<>();
        for (Entry operation : operations) {
            if (operation.getDependentOnOrderIds() != null) {
                for (Integer childGroupId : operation.getDependentOnOrderIds()) {
                    if (childGroupId != null) {
                        childGroupsByParentOp
                                .computeIfAbsent(operation.getId(), key -> new HashSet<>())
                                .add(childGroupId);
                    }
                }
            }
            if (operation.getTargetFinishedOperationId() != null) {
                for (Integer parentOperationId : operation.getTargetFinishedOperationId()) {
                    if (parentOperationId != null) {
                        childGroupsByParentOp
                                .computeIfAbsent(parentOperationId, key -> new HashSet<>())
                                .add(operation.getGroupId());
                    }
                }
            }
        }
        return childGroupsByParentOp.entrySet().stream()
                .filter(entry -> entry.getValue() != null && !entry.getValue().isEmpty())
                .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().stream()
                        .filter(Objects::nonNull)
                        .collect(Collectors.toSet())));
    }
}
