package com.utopiaindustries.service;


import com.utopiaindustries.dao.ctp.*;
import com.utopiaindustries.model.ctp.*;
import com.utopiaindustries.querybuilder.ctp.SummaryInventoryReportQueryBuilder;
import com.utopiaindustries.util.CTPDateTimeFormat;
import com.utopiaindustries.util.StringUtils;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
public class ReportingService {

    private final JobCardItemDAO jobCardItemDAO;
    private final BundleDAO bundleDAO;
    private final InventoryTransactionLegDAO inventoryTransactionLegDAO;
    private final JobCardDAO jobCardDAO;
    private final FinishedItemDAO finishedItemDAO;
    private final StitchingOfflineItemDAO stitchingOfflineItemDAO;
    private final InventoryAccountDAO inventoryAccountDAO;
    private final PurchaseOrderCTPDao purchaseOrderCTPDao;
    private final StoreItemDao storeItemDao;
    private final PackagingItemsDAO packagingItemsDAO;

    public ReportingService(JobCardItemDAO jobCardItemDAO, BundleDAO bundleDAO, InventoryTransactionLegDAO inventoryTransactionLegDAO, JobCardDAO jobCardDAO, FinishedItemDAO finishedItemDAO, StitchingOfflineItemDAO stitchingOfflineItemDAO, InventoryAccountDAO inventoryAccountDAO, PurchaseOrderCTPDao purchaseOrderCTPDao, StoreItemDao storeItemDao, PackagingItemsDAO packagingItemsDAO) {
        this.jobCardItemDAO = jobCardItemDAO;
        this.bundleDAO = bundleDAO;
        this.inventoryTransactionLegDAO = inventoryTransactionLegDAO;
        this.jobCardDAO = jobCardDAO;
        this.finishedItemDAO = finishedItemDAO;
        this.stitchingOfflineItemDAO = stitchingOfflineItemDAO;
        this.inventoryAccountDAO = inventoryAccountDAO;
        this.purchaseOrderCTPDao = purchaseOrderCTPDao;
        this.storeItemDao = storeItemDao;
        this.packagingItemsDAO = packagingItemsDAO;
    }

    public Map<String, Integer> getJobCardProgress(String jobCardID) {
        if (jobCardID == null) {
            return new LinkedHashMap<>();
        } else {
            Map<String, Integer> totalProgress = new HashMap<>();
            List<JobCardItem> jobCardItems = jobCardItemDAO.findByCardId(Long.parseLong(jobCardID));

            BigDecimal totalProduction = jobCardItems.stream()
                    .map(item -> Optional.ofNullable(item.getTotalProduction()).orElse(BigDecimal.ZERO))
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            BigDecimal actualProduction = jobCardItems.stream()
                    .map(item -> Optional.ofNullable(item.getActualProduction()).orElse(BigDecimal.ZERO))
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            BigDecimal expectedProduction = jobCardItems.stream()
                    .map(item -> Optional.ofNullable(item.getExpectedProduction()).orElse(BigDecimal.ZERO))
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            // Cutting Progress Calculation
            if (actualProduction.compareTo(BigDecimal.ZERO) == 0) {
                totalProgress.put("Cutting Progress", 0);
            } else if (actualProduction.compareTo(expectedProduction) < 0) {
                BigDecimal cuttingProgress = actualProduction
                        .divide(expectedProduction, 4, RoundingMode.HALF_UP)
                        .multiply(BigDecimal.valueOf(100));
                totalProgress.put("Cutting Progress", cuttingProgress.intValue());
            } else {
                totalProgress.put("Cutting Progress", 100);
            }

            // Stitching Progress Calculation
            if (totalProduction.compareTo(BigDecimal.ZERO) == 0) {
                totalProgress.put("Stitching Progress", 0);
            } else {
                BigDecimal stitchingProgress = totalProduction
                        .divide(actualProduction, 4, RoundingMode.HALF_UP)
                        .multiply(BigDecimal.valueOf(100));
                totalProgress.put("Stitching Progress", stitchingProgress.intValue());
            }

            // QA Progress Calculation
            Long qaProgressItems = stitchingOfflineItemDAO.CalculateTotalQA(Long.parseLong(jobCardID));
            if (qaProgressItems == 0) {
                totalProgress.put("QA Progress", 0);
            } else {
                BigDecimal qaProgress = BigDecimal.valueOf(qaProgressItems)
                        .divide(actualProduction, 4, RoundingMode.HALF_UP)
                        .multiply(BigDecimal.valueOf(100));
                totalProgress.put("QA Progress", qaProgress.intValue());
            }

            // Finishing Progress Calculation
            Long totalFinishItem = finishedItemDAO.calculateTotalFinishItem(Long.parseLong(jobCardID));
            if (totalFinishItem == 0) {
                totalProgress.put("Finishing Progress", 0);
            } else {
                BigDecimal finishItemProgress = BigDecimal.valueOf(totalFinishItem)
                        .divide(actualProduction, 4, RoundingMode.HALF_UP)
                        .multiply(BigDecimal.valueOf(100));
                totalProgress.put("Finishing Progress", finishItemProgress.intValue());
            }

            // Job Card Progress Calculation
            if (totalProduction.compareTo(BigDecimal.ZERO) == 0 && actualProduction.compareTo(BigDecimal.ZERO) == 0) {
                totalProgress.put("Job Card Progress", 0);
            } else {
                BigDecimal progressPercentage;
                if (actualProduction.compareTo(expectedProduction) > 0) {
                    progressPercentage = actualProduction.add(totalProduction)
                            .add(BigDecimal.valueOf(qaProgressItems))
                            .add(BigDecimal.valueOf(totalFinishItem))
                            .divide(actualProduction.multiply(BigDecimal.valueOf(4)), 4, RoundingMode.HALF_UP)
                            .multiply(BigDecimal.valueOf(100));
                } else {
                    progressPercentage = actualProduction.add(totalProduction)
                            .add(BigDecimal.valueOf(qaProgressItems))
                            .add(BigDecimal.valueOf(totalFinishItem))
                            .divide(expectedProduction.multiply(BigDecimal.valueOf(4)), 4, RoundingMode.HALF_UP)
                            .multiply(BigDecimal.valueOf(100));
                }
                totalProgress.put("Job Card Progress", progressPercentage.intValue());
            }

            // Sorting Map based on Custom Order
            Map<String, Integer> indexMap = new HashMap<>();
            indexMap.put("Cutting Progress", 1);
            indexMap.put("Stitching Progress", 2);
            indexMap.put("QA Progress", 3);
            indexMap.put("Finishing Progress", 4);
            indexMap.put("Job Card Progress", 5);

            return totalProgress.entrySet()
                    .stream()
                    .sorted(Comparator.comparingInt(entry -> indexMap.get(entry.getKey())))
                    .collect(LinkedHashMap::new, (m, v) -> m.put(v.getKey(), v.getValue()), Map::putAll);
        }
    }


    public Integer getTotalProduction(String jobCardID){
        if (jobCardID == null){
            return 0;
        }else {
            List<JobCardItem> jobCardItems = jobCardItemDAO.findByCardId(Long.parseLong(jobCardID));
            BigDecimal actualProduction = jobCardItems.stream()
                    .map(item -> Optional.ofNullable(item.getActualProduction()).orElse(BigDecimal.ZERO))
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            return actualProduction.intValue();
        }
    }

    public Map<String, Integer> getCompleteProduction(String jobCardID) {
        if (jobCardID == null) {
            return new LinkedHashMap<>();
        } else {
            HashMap<String, Integer> totalProgress = new HashMap<>();
            List<JobCardItem> jobCardItems = jobCardItemDAO.findByCardId(Long.parseLong(jobCardID));

            BigDecimal totalProduction = jobCardItems.stream()
                    .map(item -> Optional.ofNullable(item.getTotalProduction()).orElse(BigDecimal.ZERO))
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            BigDecimal actualProduction = jobCardItems.stream()
                    .map(item -> Optional.ofNullable(item.getActualProduction()).orElse(BigDecimal.ZERO))
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            Long qaProgressItems = stitchingOfflineItemDAO.CalculateTotalQA(Long.parseLong(jobCardID));
            Long totalFinishItem = finishedItemDAO.calculateTotalFinishItem(Long.parseLong(jobCardID));

            // Get Job card Complete Items
            Map<String, Integer> jobCardCompleteItems = getSegregateItems(jobCardID);
            Integer segregateTotalItems = jobCardCompleteItems.values().stream().mapToInt(Integer::intValue).sum();

            // Add values to totalProgress map
            totalProgress.put("Cutting Progress", actualProduction.intValue());
            totalProgress.put("Stitching Progress", totalProduction.intValue());
            totalProgress.put("QA Progress", qaProgressItems.intValue());
            totalProgress.put("Finishing Progress", totalFinishItem.intValue());
            totalProgress.put("Job Card Progress", segregateTotalItems);

            //custom order
            Map<String, Integer> indexMap = new HashMap<>();
            indexMap.put("Job Card Progress", 1);
            indexMap.put("Cutting Progress", 2);
            indexMap.put("Stitching Progress", 3);
            indexMap.put("Finishing Progress", 4);
            indexMap.put("QA Progress", 5);

            // Sort the keys based on custom order
            return totalProgress.entrySet()
                    .stream()
                    .sorted(Comparator.comparingInt(entry -> indexMap.get(entry.getKey())))
                    .collect(LinkedHashMap::new, (m, v) -> m.put(v.getKey(), v.getValue()), Map::putAll);
        }
    }


    public Map<String,Integer> getSegregateItems(String jobCardID){
        if (jobCardID == null){
            return new HashMap<>();
        }else {
            HashMap<String,Integer> gradingItems = new HashMap<>();
            List<FinishedItem> finishedItems = finishedItemDAO.findByJobCardId(Long.parseLong(jobCardID));

            List<FinishedItem> bGradeFinishItemsIds= finishedItems.stream()
                    .filter(item -> "REJECT".equals(item.getQaStatus())).collect(Collectors.toList());

            List<FinishedItem> cGradeFinishItemsIds= finishedItems.stream()
                    .filter(item -> "C GRADE".equals(item.getQaStatus())).collect(Collectors.toList());

            List<FinishedItem> aGradeFinishItemsIds= finishedItems.stream()
                    .filter(item -> "APPROVED".equals(item.getQaStatus())).collect(Collectors.toList());


            if (finishedItems.isEmpty()){
                gradingItems.put("A GRADE",0);
                gradingItems.put("B GRADE",0);
                gradingItems.put("C GRADE",0);
                return gradingItems;
            }else {
                gradingItems.put("B GRADE",bGradeFinishItemsIds.size());
                gradingItems.put("C GRADE",cGradeFinishItemsIds.size());
                gradingItems.put("A GRADE",aGradeFinishItemsIds.size());
                return gradingItems;
            }
        }
    }

    public HashMap<String,String> getEachPhaseTotalTime(String jobCardID){
        if (jobCardID == null){
            return new HashMap<>();
        }else {
            HashMap<String,String> phasesTimes = new HashMap<>();
            JobCard jobCard = jobCardDAO.find(Long.parseLong(jobCardID));
            Bundle lastBundle = bundleDAO.findLastBundleByCardId(Long.parseLong(jobCardID));

            //Cutting time
            StringBuilder cuttingTime = generateTime(jobCard.getCreatedAt(),lastBundle.getCreatedAt());
            phasesTimes.put("Cutting Total Time",cuttingTime.toString());

            //Stitching time
            List<Long> bundlesIds = bundleDAO.findByCardId(Long.parseLong(jobCardID)).stream().map(Bundle::getId).collect(Collectors.toList());
            if (!bundlesIds.isEmpty()){
                InventoryTransactionLeg inventoryTransactionLeg = inventoryTransactionLegDAO.getFirstStitchBundleTime(bundlesIds,"STITCH_BUNDLE");
                StitchingOfflineItem lastStitchItem = stitchingOfflineItemDAO.getLastStitchItemByCardIdAndTime(Long.parseLong(jobCardID));
                StringBuilder stitchingTime = generateTime(inventoryTransactionLeg.getTransactionLegDateTime(),lastStitchItem.getCreatedAt());
                phasesTimes.put("Stitching Total Time",stitchingTime.toString());
            }else {
                phasesTimes.put("Stitching Total Time",null);

            }

            return phasesTimes;
        }
    }

    public HashMap<String,String> countPendingItemsOnDifferentPhases(String jobCardID){
        if (jobCardID == null){
            return new HashMap<>();
        }else {
            HashMap<String,String> phasePending = new HashMap<String,String>();
            List<JobCardItem> jobCardItems = jobCardItemDAO.findByCardId(Long.parseLong(jobCardID));
            BigDecimal totalProduction = jobCardItems.stream()
                    .map(item -> Optional.ofNullable(item.getTotalProduction()).orElse(BigDecimal.ZERO))
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            BigDecimal actualProduction = jobCardItems.stream()
                    .map(item -> Optional.ofNullable(item.getActualProduction()).orElse(BigDecimal.ZERO))
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            if(actualProduction.compareTo(totalProduction) == 0) {
                phasePending.put("Stitching Total Time", null);
            }else {
                phasePending.put("Stitching Total Time", "Pending");
            }
            return phasePending;
        }
    }

    public Map<String, Map<String, Object>> getCuttingDetails(String jobCardID) {
        if (jobCardID == null) {
            return Collections.emptyMap();
        }

        Map<String, Map<String, Object>> cuttingDetails = new HashMap<>();
        Map<String, InventoryAccount> cuttingAccount = new HashMap<>();
        Map<String, String> cuttingPersonName = new HashMap<>();
        Map<String, LocalDate> cuttingDate = new HashMap<>();

        List<Bundle> bundles = bundleDAO.findByCardId(Long.parseLong(jobCardID));
        if (bundles.isEmpty()) {
            return cuttingDetails;
        }

        Map<Long, Bundle> bundleMap = bundles.stream()
                .collect(Collectors.toMap(Bundle::getId, Function.identity()));

        List<Long> bundleIds = new ArrayList<>(bundleMap.keySet());
        List<InventoryTransactionLeg> inventoryTransactionLegs = inventoryTransactionLegDAO
                .getTransactionByParentIdAndType(bundleIds, "BUNDLE");

        for (InventoryTransactionLeg inventoryTransactionLeg : inventoryTransactionLegs) {
            Integer accountId = inventoryTransactionLeg.getAccountId();
            InventoryAccount inventoryAccount = inventoryAccountDAO.find(accountId.longValue());
            if (inventoryAccount != null) {
                String accountTitle = inventoryAccount.getTitle();
                cuttingAccount.put(accountTitle, inventoryAccount);
                Bundle bundle = bundleMap.get(inventoryTransactionLeg.getParentDocumentId());
                cuttingPersonName.put(accountTitle, (bundle != null) ? bundle.getCreatedBy() : null);
                cuttingDate.put(accountTitle,(bundle != null) ? bundle.getCreatedAt().toLocalDate() : null);
            }
        }

        cuttingDetails.put("accounts", new HashMap<>(cuttingAccount));
        cuttingDetails.put("personName", new HashMap<>(cuttingPersonName));
        cuttingDetails.put("date", new HashMap<>(cuttingDate));

        return cuttingDetails;
    }

    public Map<String, Map<String, Object>> getStitchingDetails(String jobCardID) {
        if (jobCardID == null) {
            return Collections.emptyMap();
        }

        Map<String, Map<String, Object>> stitchingDetails = new HashMap<>();
        Map<String, InventoryAccount> stitchingAccount = new HashMap<>();
        Map<String, String> stitchingPersonName = new HashMap<>();
        Map<String, LocalDate> stitchingDate = new HashMap<>();

        List<StitchingOfflineItem> stitchingOfflineItems = stitchingOfflineItemDAO.findByJobCardId(Long.parseLong(jobCardID));
        if (stitchingOfflineItems.isEmpty()) {
            return stitchingDetails;
        }

        Map<Long, StitchingOfflineItem> stitchingOfflineItemMap = stitchingOfflineItems.stream()
                .collect(Collectors.toMap(StitchingOfflineItem::getId, Function.identity()));

        List<Long> stitchingItemsArray = new ArrayList<>(stitchingOfflineItemMap.keySet());
        List<InventoryTransactionLeg> inventoryTransactionLegs = inventoryTransactionLegDAO
                .getTransactionByParentIdAndType(stitchingItemsArray, "STITCHING_OFFLINE");

        for (InventoryTransactionLeg inventoryTransactionLeg : inventoryTransactionLegs) {
            Integer accountId = inventoryTransactionLeg.getAccountId();
            InventoryAccount inventoryAccount = inventoryAccountDAO.find(accountId.longValue());
            if (inventoryAccount != null) {
                String accountTitle = inventoryAccount.getTitle();
                stitchingAccount.put(accountTitle, inventoryAccount);
                StitchingOfflineItem stitchingOfflineItem = stitchingOfflineItemMap.get(inventoryTransactionLeg.getParentDocumentId());
                stitchingPersonName.put(accountTitle, (stitchingOfflineItem != null) ? stitchingOfflineItem.getCreatedBy() : null);
                stitchingDate.put(accountTitle,(stitchingOfflineItem != null) ? stitchingOfflineItem.getCreatedAt().toLocalDate() : null);
            }
        }

        stitchingDetails.put("accounts", new HashMap<>(stitchingAccount));
        stitchingDetails.put("personName", new HashMap<>(stitchingPersonName));
        stitchingDetails.put("date", new HashMap<>(stitchingDate));

        return stitchingDetails;
    }


    public Map<String, List<?>> getPhasesProgressDayWise(String jobCardID) {
        if (jobCardID == null) {
            return Collections.emptyMap();
        }
        List<InventoryAccount> inventoryAccounts = inventoryAccountDAO.findByParentEntityTypeAndParentId("PROCESS",6L);

        LocalDateTime startDate = jobCardDAO.find(Long.parseLong(jobCardID)).getCreatedAt();
        HashMap<String, List<?>> barChartData = new HashMap<>();

        List<InventoryTransactionLeg> inventoryTransactionLegs = inventoryTransactionLegDAO
                .getTransactionByJobCardAndDatesAndType(Long.parseLong(jobCardID), startDate.toString(), LocalDateTime.now().toString(), "IN");

        //remove quality multiple transaction entry approved/rejects
        List<InventoryTransactionLeg> filteredTransactions = inventoryTransactionLegs.stream()
                .collect(Collectors.toMap(
                        leg -> leg.getAccountId() + "-" + leg.getParentDocumentType() + "-" + leg.getParentDocumentId(),
                        leg -> leg, // Value
                        (existing, replacement) -> existing.getTransactionLegDateTime().isBefore(replacement.getTransactionLegDateTime()) ? existing : replacement // Keep Oldest
                ))
                .values()
                .stream()
                .collect(Collectors.toList());

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

        List<String> uniqueDates = inventoryTransactionLegs.stream()
                .map(leg -> leg.getTransactionLegDateTime().toLocalDate().format(formatter))
                .distinct()
                .sorted()
                .collect(Collectors.toList());

        Map<String, Integer> dateIndexMap = new HashMap<>();
        for (int i = 0; i < uniqueDates.size(); i++) {
            dateIndexMap.put(uniqueDates.get(i), i);
        }

        List<Integer> cuttingList = new ArrayList<>(Collections.nCopies(uniqueDates.size(), 0));
        List<Integer> stitchingList = new ArrayList<>(Collections.nCopies(uniqueDates.size(), 0));
        List<Integer> qualityList = new ArrayList<>(Collections.nCopies(uniqueDates.size(), 0));
        List<Integer> finishItems = new ArrayList<>(Collections.nCopies(uniqueDates.size(), 0));

        for (InventoryTransactionLeg leg : filteredTransactions) {
            String dateKey = leg.getTransactionLegDateTime().format(formatter);
            int index = dateIndexMap.get(dateKey);
            if ("BUNDLE".equals(leg.getParentDocumentType())) {
                Bundle bundle = bundleDAO.find(leg.getParentDocumentId());
                cuttingList.set(index, cuttingList.get(index) + bundle.getWrapQuantity().intValue());
            }
            else if ("STITCHING_OFFLINE".equals(leg.getParentDocumentType())) {
                stitchingList.set(index, stitchingList.get(index) + leg.getQuantity().intValue());
            }
            else if ("FINISHED_ITEM".equals(leg.getParentDocumentType()) && (leg.getAccountId().equals(7) || leg.getAccountId().equals(12))) {
                qualityList.set(index, qualityList.get(index) + leg.getQuantity().intValue());
            }
            else if ("PACKAGING".equals(leg.getParentDocumentType()) && inventoryAccounts.stream().anyMatch(e -> e.getId() == leg.getAccountId().longValue())) {
                finishItems.set(index, finishItems.get(index) + leg.getQuantity().intValue());
            }
        }

        barChartData.put("dates", uniqueDates);
        barChartData.put("cutting", cuttingList);
        barChartData.put("stitching", stitchingList);
        barChartData.put("quality", qualityList);
        barChartData.put("finishing", finishItems);

        return barChartData;
    }

    /*
     * find all po for po-online-status
     * */
    public List<POsDetails> getAllPOs(String poCode, String size, String color, Long count) {

        List<POsDetails> pOsDetailsList = new ArrayList<>();
        List<PurchaseOrderCTP> purchaseOrderCTPList;

        if (poCode != null && !poCode.isEmpty()) {
            purchaseOrderCTPList = purchaseOrderCTPDao.findByPoCode(poCode, count);
        }else {
            purchaseOrderCTPList = purchaseOrderCTPDao.findAll(count);
        }


        for (PurchaseOrderCTP pos : purchaseOrderCTPList) {
            List<JobCard> jobCards = jobCardDAO.findByPoId(pos.getId());
            BigDecimal totalProduction = BigDecimal.ZERO;
            BigDecimal expectedProduction = BigDecimal.ZERO;
            BigDecimal actualProduction = BigDecimal.ZERO;
            long stitchingIn = 0L;
            long stitchingOut = 0L;
            long finishApprovedItem = 0L;
            long finishRejectItem = 0L;
            long storeItems = 0L;
            long packagingItems = 0L;
            POsDetails pOsDetails = new POsDetails();

            List<Long> jobCardIds = jobCards.stream()
                    .map(JobCard::getId)
                    .collect(Collectors.toList());
            List<JobCardItem> jobCardItems = new ArrayList<>();
            if (!jobCardIds.isEmpty()) {
                jobCardItems = jobCardItemDAO.findByJobCardIdsAndSizeColor(jobCardIds,size, color);
            }else {
                jobCardItems = new ArrayList<>();
            }

            expectedProduction = expectedProduction.add(jobCardItems.stream()
                    .map(item -> Optional.ofNullable(item.getExpectedProduction()).orElse(BigDecimal.ZERO))
                    .reduce(BigDecimal.ZERO, BigDecimal::add));

            actualProduction = actualProduction.add(jobCardItems.stream()
                    .map(item -> Optional.ofNullable(item.getActualProduction()).orElse(BigDecimal.ZERO))
                    .reduce(BigDecimal.ZERO, BigDecimal::add));
            if(!jobCardItems.isEmpty() || (StringUtils.isNullOrEmpty(size)  || StringUtils.isNullOrEmpty(color))) {
                for (JobCardItem jobCardItem : jobCardItems) {

                    //stitching detail
                    stitchingIn += Optional.of(stitchingOfflineItemDAO.findByJobCardId(jobCardItem.getJobCardId()).size()).orElse(0);
                    stitchingOut += Optional.ofNullable(stitchingOfflineItemDAO.CalculateTotalQA(jobCardItem.getJobCardId())).orElse(0L);

                    //finishItems detail
                    List<FinishedItem> finishedItems = finishedItemDAO.findByJobCardId(jobCardItem.getJobCardId());
                    finishApprovedItem += finishedItems.stream().filter(e -> e.getQaStatus().equals("APPROVED")).count();
                    finishRejectItem += finishedItems.stream().filter(e -> e.getQaStatus().equals("REJECT")).count();

                    //reject store details
                    storeItems += Optional.ofNullable(storeItemDao.calculateTotalRejectItemByJobCardId(jobCardItem.getJobCardId())).orElse(0L);

                    //reject packaging details
                    packagingItems += Optional.of(packagingItemsDAO.findByJobCardId(jobCardItem.getJobCardId()).size()).orElse(0);

                }
                pOsDetails.setPoId(pos.getId());
                pOsDetails.setPoNumber(pos.getPurchaseOrderCode());
                pOsDetails.setArticleTitle(pos.getArticleName());
                pOsDetails.setPoQuantity(pos.getPurchaseOrderQuantity());
                pOsDetails.setPoRequiredQuantity(pos.getPurchaseOrderQuantityRequired());
                pOsDetails.setActualCutting(expectedProduction.longValue());
                pOsDetails.setBalanceToCutting(pos.getPurchaseOrderQuantityRequired() - expectedProduction.longValue());
                pOsDetails.setCuttingReceived(expectedProduction.longValue());
                pOsDetails.setCuttingOki(expectedProduction.intValue());
                pOsDetails.setCuttingReject(expectedProduction.subtract(expectedProduction).intValue());
                pOsDetails.setStitchingIn(stitchingIn);
                pOsDetails.setStitchingOut(stitchingOut);
                pOsDetails.setStitchingWips(stitchingIn - stitchingOut);
                pOsDetails.setFinishIn(stitchingOut);
                pOsDetails.setFinishRej(finishRejectItem);
                pOsDetails.setFinishQaApproved(finishApprovedItem);
                pOsDetails.setStoreReceived(storeItems);
                pOsDetails.setStoreWaiting(finishRejectItem - storeItems);
                pOsDetails.setFinishQaApproved(finishApprovedItem);
                pOsDetails.setPackagingIn(packagingItems);
                pOsDetails.setPackagingOut(packagingItems);
                pOsDetails.setPackagingStock(0);
                pOsDetails.setShippedScan(packagingItems);
                pOsDetails.setShippedNet(packagingItems);
                pOsDetails.setPackagingStock(0);
                pOsDetails.setPoStatus(pos.getPoStatus());
                pOsDetailsList.add(pOsDetails);
            }
        }
        return pOsDetailsList;
    }

    /*
     * find all job cards of specific po
     * */
    public HashMap<String, Map<String, Integer>> getAllPoJobCards(long poId, String selectDate) {
        String startDate = selectDate != null && !selectDate.isEmpty() ? selectDate + " 00:00:01": null;
        String endDate = selectDate != null && !selectDate.isEmpty() ? selectDate + " 23:59:59": null;

        HashMap<String, Map<String, Integer>> poJobCardItemsProgress = new HashMap<>();
        // Filter JobCards by Purchase Order ID
        List<JobCard> filterJobCardsByPos = jobCardDAO.findByPoId(poId);
        List<InventoryAccount> inventoryAccounts = inventoryAccountDAO.getPackagingAccounts();
        List<Integer> gradingAccounts = inventoryAccounts.stream().map(e-> (int)(e.getId())).collect(Collectors.toList());
        for (JobCard jobCard : filterJobCardsByPos) {
            List<Bundle> bundles = bundleDAO.findByCardIdAndDATE(jobCard.getId(),startDate,endDate);
            List<StitchingOfflineItem> stitchingOfflineItems = stitchingOfflineItemDAO.findByJobCardIdAndDate(jobCard.getId(),startDate,endDate);
            List<FinishedItem> finishedItems = finishedItemDAO.calculateTotalFinishItem(jobCard.getId(),startDate,endDate);

            //cutting days wise
            BigDecimal cutting = bundles.stream()
                    .map(bundle -> Optional.ofNullable(bundle.getWrapQuantity()).orElse(BigDecimal.ZERO))
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            //stitching day wise
            Integer stitching = stitchingOfflineItems.size();

            //total qa
            Integer qa = finishedItems.size();
            Map<String, Integer> segregateItems = finishedItems.stream()
                    .collect(Collectors.groupingBy(
                            FinishedItem::getQaStatus,
                            Collectors.collectingAndThen(
                                    Collectors.counting(),
                                    Long::intValue
                            )
                    ));

            Map<String, Integer> items = getCompleteProduction(String.valueOf(jobCard.getId()));
            items.put("Cutting Progress",cutting.intValue());
            items.put("Stitching Progress",stitching);
            items.put("QA Progress",qa);
            items.put("A Grade",segregateItems.get("APPROVED") != null ? segregateItems.get("APPROVED") : 0);
            items.put("B Grade / Reject",segregateItems.get("REJECT") != null ? segregateItems.get("REJECT") : 0);

            // Define sorting order
            Map<String, Integer> indexMap = new HashMap<>();
            indexMap.put("Job Card Progress", 1);
            indexMap.put("Cutting Progress", 2);
            indexMap.put("Stitching Progress", 3);
            indexMap.put("QA Progress", 4);
            indexMap.put("Finishing Progress", 5);
            indexMap.put("APPROVED", 6);
            indexMap.put("REJECT", 7);

            // Sort items based on indexMap order
            Map<String, Integer> sortedItems = items.entrySet()
                    .stream()
                    .sorted(Comparator.comparingInt(entry -> indexMap.getOrDefault(entry.getKey(), Integer.MAX_VALUE)))
                    .collect(Collectors.toMap(
                            Map.Entry::getKey,
                            Map.Entry::getValue,
                            (oldValue, newValue) -> oldValue,
                            LinkedHashMap::new
                    ));

            // Add sorted items to the result map
            poJobCardItemsProgress.put(jobCard.getCode(), sortedItems);
        }

        return poJobCardItemsProgress;
    }

    //get po related items size, poId or color
    public HashMap<String, PoItemsWrapper> getPoItemsSizeOrColor(long poId, String size, String color) {
        List<JobCard> jobCards = jobCardDAO.findByPoId(poId);
        List<Long> jobCardIds = jobCards.stream()
                .map(JobCard::getId)
                .collect(Collectors.toList());

        HashMap<String, PoItemsWrapper> poItemsWrapperHashMap = new HashMap<>();

        if(!jobCardIds.isEmpty()){
            List<JobCardItem> jobCardItems = jobCardItemDAO.findByJobCardIdsAndSizeColor(jobCardIds, size, color);
            jobCardItems.forEach(e -> {
                String sku = e.getSku();
                PoItemsWrapper poItemsWrapper = poItemsWrapperHashMap.getOrDefault(sku, new PoItemsWrapper());
                poItemsWrapper.setColor(e.getColor());
                poItemsWrapper.setSize(e.getSize());
                poItemsWrapper.setSku(e.getSku());
                poItemsWrapper.setTotalCutting(
                        poItemsWrapper.getTotalCutting() + (e.getActualProduction() != null ? e.getActualProduction().intValue() : 0)
                );
                poItemsWrapper.setTotalProduction(
                        poItemsWrapper.getTotalProduction() + (e.getExpectedProduction() != null ? e.getExpectedProduction().intValue() : 0)
                );

                poItemsWrapperHashMap.put(sku, poItemsWrapper);
            });
        }
        return poItemsWrapperHashMap;
    }

    public Map<String, Object> getCuttingTableDetails(String jobCardId, String cuttingTableId, String startDate, String endDate, Long count) {
        Map<String, Object> cuttingDetails = new HashMap<>();
        long jobCardIdTemp = 0L;
        String startDate1 = null;
        String endDate1 = null;

        if (!StringUtils.isNullOrEmpty(startDate)) {
            String formattedStart = CTPDateTimeFormat.getMySQLFormattedDateString(startDate, CTPDateTimeFormat.HTML5_DATE_INPUT_FORMAT);
            String formattedEnd = !StringUtils.isNullOrEmpty(endDate)
                    ? CTPDateTimeFormat.getMySQLFormattedDateString(endDate, CTPDateTimeFormat.HTML5_DATE_INPUT_FORMAT)
                    : LocalDate.now().toString();

            startDate1 = String.format("'%s 00:00:01'", formattedStart);
            endDate1 = String.format("'%s 23:59:59'", formattedEnd);
        }

        List<InventoryAccount> inventoryAccounts = inventoryAccountDAO.findByParentEntityTypeAndParentId("PROCESS", 1L);
        List<Long> inventoryAccountIds = inventoryAccounts.stream().map(InventoryAccount::getId).collect(Collectors.toList());

        if (!StringUtils.isNullOrEmpty(jobCardId)) {
            jobCardIdTemp = Long.parseLong(jobCardId);
        } else if (!StringUtils.isNullOrEmpty(cuttingTableId)) {
            inventoryAccountIds = List.of(Long.parseLong(cuttingTableId));
        }

        String query = SummaryInventoryReportQueryBuilder.cuttingQueryBuild(jobCardIdTemp, inventoryAccountIds, null, startDate1, endDate1, "IN","BUNDLE", count);
        List<InventoryTransactionLeg> inventoryTransactionLegs = inventoryTransactionLegDAO.findByQuery(query);

        Map<String, Integer> dateWiseProduction = new TreeMap<>();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");

        Map<LocalDate, List<Long>> dateWiseJobCardIds = inventoryTransactionLegs.stream()
                .filter(e -> e.getTransactionLegDateTime() != null && e.getJobCardId() != 0)
                .collect(Collectors.groupingBy(
                        e -> e.getTransactionLegDateTime().toLocalDate(),
                        Collectors.mapping(InventoryTransactionLeg::getJobCardId, Collectors.toList())
                ));

        for (Map.Entry<LocalDate, List<Long>> entry : dateWiseJobCardIds.entrySet()) {
            List<Long> jobCardIds = entry.getValue();
            if (!jobCardIds.isEmpty()) {
                List<JobCardItem> jobCardItems = jobCardItemDAO.findByJobCardIdsAndSizeColor(jobCardIds, null, null);
                int totalProduction = jobCardItems.stream()
                        .filter(item -> item.getActualProduction() != null)
                        .mapToInt(item -> item.getActualProduction().intValue())
                        .sum();
                dateWiseProduction.put(entry.getKey().format(formatter), totalProduction);
            }
        }

        List<Long> distinctJobCardIds = inventoryTransactionLegs.stream()
                .map(InventoryTransactionLeg::getJobCardId)
                .filter(id -> id != 0)
                .distinct()
                .collect(Collectors.toList());

        Map<Long, List<CuttingJobCardItemWrapper>> jobCardItemsCuttingDetailsMap = new HashMap<>();

        for (long jobCardIdEntry : distinctJobCardIds) {
            Long accountId = inventoryTransactionLegs.stream()
                    .filter(e -> e.getJobCardId() == jobCardIdEntry)
                    .map(e -> e.getAccountId().longValue())
                    .findFirst()
                    .orElse(0L);

            JobCard jobCard = jobCardDAO.find(jobCardIdEntry);
            Bundle bundle = bundleDAO.findByCardId(jobCardIdEntry).stream().findFirst().orElse(new Bundle());

            List<JobCardItem> jobCardItems = jobCardItemDAO.findByCardId(jobCardIdEntry);

            List<CuttingJobCardItemWrapper> wrappers = jobCardItems.stream().map(item -> {
                CuttingJobCardItemWrapper wrapper = new CuttingJobCardItemWrapper();
                wrapper.setArticleName(jobCard.getArticleName());
                wrapper.setJobCardCode(jobCard.getCode());
                wrapper.setGsm(item.getGsm());
                wrapper.setPly(item.getPly());
                wrapper.setSku(item.getSku());
                wrapper.setTotal(item.getActualProduction().longValue());
                wrapper.setWidth(item.getWidth());
                wrapper.setWtPly(item.getWtPly());
                wrapper.setComplete(item.isComplete());
                wrapper.setPoName(jobCard.getPurchaseOrderId());
                wrapper.setOperatorName(bundle.getCreatedBy());
                wrapper.setJobCardId(item.getJobCardId());
                wrapper.setLength(item.getLength());
                wrapper.setAccountId(accountId);
                return wrapper;
            }).collect(Collectors.toList());

            jobCardItemsCuttingDetailsMap.computeIfAbsent(accountId, k -> new ArrayList<>()).addAll(wrappers);
        }

        cuttingDetails.put("Date Wise Cutting", dateWiseProduction);
        cuttingDetails.put("accountWiseCutting", jobCardItemsCuttingDetailsMap);
        cuttingDetails.put("cuttingAccount", inventoryAccounts);
        return cuttingDetails;
    }

    public Map<String, Object> getStitchingDetails(String jobCardId, String stitchingLine, String startDate, String endDate, Long count) {
        Map<String, Object> stitchingDetails = new HashMap<>();
        long jobCardIdTemp = 0L;
        String startDate1 = null;
        String endDate1 = null;

        if (!StringUtils.isNullOrEmpty(startDate)) {
            String formattedStart = CTPDateTimeFormat.getMySQLFormattedDateString(startDate, CTPDateTimeFormat.HTML5_DATE_INPUT_FORMAT);
            String formattedEnd = !StringUtils.isNullOrEmpty(endDate)
                    ? CTPDateTimeFormat.getMySQLFormattedDateString(endDate, CTPDateTimeFormat.HTML5_DATE_INPUT_FORMAT)
                    : LocalDate.now().toString();

            startDate1 = String.format("'%s 00:00:01'", formattedStart);
            endDate1 = String.format("'%s 23:59:59'", formattedEnd);
        }

        List<InventoryAccount> inventoryAccounts = inventoryAccountDAO.findByParentEntityTypeAndParentId("PROCESS", 2L);
        List<Long> inventoryAccountIds = inventoryAccounts.stream().map(InventoryAccount::getId).collect(Collectors.toList());

        if (!StringUtils.isNullOrEmpty(jobCardId)) {
            jobCardIdTemp = Long.parseLong(jobCardId);
        } else if (!StringUtils.isNullOrEmpty(stitchingLine)) {
            inventoryAccountIds = List.of(Long.parseLong(stitchingLine));
        }

        String query = SummaryInventoryReportQueryBuilder.cuttingQueryBuild(jobCardIdTemp, inventoryAccountIds,null, startDate1, endDate1, "IN","STITCHING_OFFLINE", count);
        List<InventoryTransactionLeg> inventoryTransactionLegs = inventoryTransactionLegDAO.findByQuery(query);

        Map<String, Integer> dateWiseProduction = new TreeMap<>();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");

        Map<LocalDate, List<Long>> dateWiseJobCardIds = inventoryTransactionLegs.stream()
                .filter(e -> e.getTransactionLegDateTime() != null && e.getJobCardId() != 0)
                .collect(Collectors.groupingBy(
                        e -> e.getTransactionLegDateTime().toLocalDate(),
                        Collectors.mapping(InventoryTransactionLeg::getJobCardId, Collectors.toList())
                ));

        for (Map.Entry<LocalDate, List<Long>> entry : dateWiseJobCardIds.entrySet()) {
            LocalDate date = entry.getKey();
            List<Long> jobCardIds = entry.getValue();

            if (!jobCardIds.isEmpty()) {
                int totalProduction = inventoryTransactionLegs.stream()
                        .filter(item -> jobCardIds.contains(item.getJobCardId()) &&
                                "STITCHING_OFFLINE".equals(item.getParentDocumentType()) && item.getTransactionLegDateTime().toLocalDate().equals(date))
                        .mapToInt(item -> item.getQuantity().intValue())
                        .sum();

                dateWiseProduction.put(date.format(formatter), totalProduction);
            }
        }

        List<Long> distinctJobCardIds = inventoryTransactionLegs.stream()
                .map(InventoryTransactionLeg::getJobCardId)
                .filter(id -> id != 0)
                .distinct()
                .collect(Collectors.toList());

        Map<Long, List<CuttingJobCardItemWrapper>> jobCardItemsStitchingDetailsMap = new HashMap<>();
        Map<String, Integer> totalStitchingBasedOnAccountID = new HashMap<>();

        for (long jobCardIdEntry : distinctJobCardIds) {
            Long accountId = inventoryTransactionLegs.stream()
                    .filter(e -> e.getJobCardId() == jobCardIdEntry)
                    .map(e -> e.getAccountId().longValue())
                    .findFirst()
                    .orElse(0L);

            JobCard jobCard = jobCardDAO.find(jobCardIdEntry);
            List<StitchingOfflineItem> stitchingOfflineItem = stitchingOfflineItemDAO.findByJobCardId(jobCardIdEntry);

            List<JobCardItem> jobCardItems = jobCardItemDAO.findByCardId(jobCardIdEntry);

            List<CuttingJobCardItemWrapper> wrappers = jobCardItems.stream().map(item -> {
                CuttingJobCardItemWrapper wrapper = new CuttingJobCardItemWrapper();
                wrapper.setArticleName(jobCard.getArticleName());
                wrapper.setJobCardId(jobCard.getId());
                wrapper.setJobCardCode(jobCard.getCode());
                wrapper.setSku(item.getSku());
                wrapper.setPoName(jobCard.getPurchaseOrderId());
                wrapper.setJobCardId(item.getJobCardId());
                wrapper.setOperatorName(stitchingOfflineItem.get(0).getCreatedBy());
                wrapper.setAccountId(accountId);
                return wrapper;

            }).collect(Collectors.toList());
            totalStitchingBasedOnAccountID.put(jobCard.getId()+stitchingOfflineItem.get(0).getSku(), stitchingOfflineItem.size());
            jobCardItemsStitchingDetailsMap.computeIfAbsent(accountId, k -> new ArrayList<>()).addAll(wrappers);
        }

        stitchingDetails.put("totalStitchingBasedOnAccountID", totalStitchingBasedOnAccountID);
        stitchingDetails.put("jobCardItemsStitchingDetailsMap", jobCardItemsStitchingDetailsMap);
        stitchingDetails.put("Date Wise Stitching", dateWiseProduction);

        stitchingDetails.put("stitchingAccount", inventoryAccounts);
        return stitchingDetails;
    }

    public List<StitchingOfflineItem> stitchingItemsTransactions(String jobCardId, String accountId, String sku, String startDate, String endDate, Long count) {
        List<Long> accountID = new ArrayList<>();
        String startDate1 = null;
        String endDate1 = null;

        if (!StringUtils.isNullOrEmpty(startDate) && !StringUtils.isNullOrEmpty(accountId) && !StringUtils.isNullOrEmpty(jobCardId)) {
            String formattedStart = CTPDateTimeFormat.getMySQLFormattedDateString(startDate, CTPDateTimeFormat.HTML5_DATE_INPUT_FORMAT);
            String formattedEnd = !StringUtils.isNullOrEmpty(endDate)
                    ? CTPDateTimeFormat.getMySQLFormattedDateString(endDate, CTPDateTimeFormat.HTML5_DATE_INPUT_FORMAT)
                    : LocalDate.now().toString();

            startDate1 = String.format("'%s 00:00:01'", formattedStart);
            endDate1 = String.format("'%s 23:59:59'", formattedEnd);
            accountID.add(Long.parseLong(accountId));
        }


        String query = SummaryInventoryReportQueryBuilder.cuttingQueryBuild(Long.parseLong(jobCardId), accountID, sku, startDate1, endDate1,"IN","STITCHING_OFFLINE", count);
        List<InventoryTransactionLeg> inventoryTransactionLegs = inventoryTransactionLegDAO.findByQuery(query);
        List<Long> stitchingItemsList = inventoryTransactionLegs.stream()
                .map(InventoryTransactionLeg::getParentDocumentId)
                .collect(Collectors.toList());
        return stitchingOfflineItemDAO.findByIds(stitchingItemsList);
    }

    private StringBuilder generateTime(LocalDateTime startDate, LocalDateTime endDate){
        StringBuilder totalTime = new StringBuilder();
        if(startDate != null && endDate != null){
            Duration duration = Duration.between(startDate, endDate);
            if (duration.toDays() > 0) {
                totalTime.append(String.format("%d days, ", duration.toDays()));
            }

            if (duration.toHours() % 24 > 0) {
                totalTime.append(String.format("%d hours, ", duration.toHours() % 24));
            }

            if (duration.toMinutes() % 60 > 0) {
                totalTime.append(String.format("%d minutes ", duration.toMinutes() % 60));
            }
            return totalTime;
        }else return totalTime.append("");
    }

}
