package com.utopiaindustries.service;

import com.utopiaindustries.dao.ctp.*;
import com.utopiaindustries.dao.uind.ItemDAO;
import com.utopiaindustries.dao.uind.LocationSiteDAO;
import com.utopiaindustries.dao.uind.PurchaseOrderDAO;
import com.utopiaindustries.model.ctp.*;
import com.utopiaindustries.model.uind.Item;
import com.utopiaindustries.querybuilder.ctp.JobCardQueryBuilder;
import com.utopiaindustries.util.StringUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
public class JobCardService {

    private final JobCardDAO jobCardDAO;
    private final CutPieceTypeDAO cutPieceTypeDAO;
    private final JobCardItemDAO jobCardItemDAO;
    private final CutPieceDAO cutPieceDAO;
    private final ItemDAO itemDAO;
    private final LocationSiteDAO locationSiteDAO;
    private final PurchaseOrderDAO purchaseOrderDAO;
    private final UserInventoryAccountDAO userInventoryAccountDAO;
    private final FinishedItemDAO finishedItemDAO;
    private final StitchingOfflineItemDAO stitchingOfflineItemDAO;

    public JobCardService(JobCardDAO jobCardDAO, CutPieceTypeDAO cutPieceTypeDAO, JobCardItemDAO jobCardItemDAO, CutPieceDAO cutPieceDAO, ItemDAO itemDAO, LocationSiteDAO locationSiteDAO, PurchaseOrderDAO purchaseOrderDAO, UserInventoryAccountDAO userInventoryAccountDAO, FinishedItemDAO finishedItemDAO, StitchingOfflineItemDAO stitchingOfflineItemDAO) {
        this.jobCardDAO = jobCardDAO;
        this.cutPieceTypeDAO = cutPieceTypeDAO;
        this.jobCardItemDAO = jobCardItemDAO;
        this.cutPieceDAO = cutPieceDAO;
        this.itemDAO = itemDAO;
        this.locationSiteDAO = locationSiteDAO;
        this.purchaseOrderDAO = purchaseOrderDAO;
        this.userInventoryAccountDAO = userInventoryAccountDAO;
        this.finishedItemDAO = finishedItemDAO;
        this.stitchingOfflineItemDAO = stitchingOfflineItemDAO;
    }

    /*
    * search by code
    * */
    public List<JobCard> searchJobCards( String term, Boolean filter ){
        List<JobCard> cards = new ArrayList<>();
        if( filter == null || filter ){
            cards =  jobCardDAO.findLikeCode( term,
                    JobCard.InventoryStatus.NOT_RECEIVED_YET.name(),
                    JobCard.Status.POSTED.name()
            );
        } else {
            cards = jobCardDAO.findLikeCode( term );
        }
        return cards;
    }


    /*
     *  get cards
     * */
    public List<JobCard> getCards( String code,
                                   String status,
                                   String inventoryStatus,
                                   String customer,
                                   String lotNumber,
                                   String purchaseOrderId,
                                   String locationSiteId,
                                   String createdStartDate,
                                   String createdEndDate,
                                   Long limit) {
        List<JobCard> jobCards = new ArrayList<>();
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String createdBy = authentication.getName();
        if( limit == null ){
            limit = 100L;
        }
        if( StringUtils.isAnyNotNullOrEmpty( code, status, inventoryStatus, customer, lotNumber, purchaseOrderId, locationSiteId, createdStartDate, createdEndDate ) ){
            for (GrantedAuthority role : authentication.getAuthorities()){
                if (role.toString().equals("ROLE_ADMIN")){
                    createdBy = "";
                }
            }
            String query = JobCardQueryBuilder.buildQuery(code, createdBy, status, inventoryStatus, customer, lotNumber, purchaseOrderId, locationSiteId, createdStartDate, createdEndDate, limit );
            System.out.println( query );
            jobCards = jobCardDAO.findByQuery( query );
        }else {
            jobCards = jobCardDAO.findByUserAndLimit( authentication.getName(), limit );
        }
        return jobCards;
    }

    /*
     *  create new job card
     * */
    public JobCard createNewJobCard() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        JobCard jobCard = new JobCard();
        jobCard.setCreatedBy( authentication.getName() );
        jobCard.setCreatedAt( LocalDateTime.now() );
        // setting fields
        List<JobCardItem> items = new ArrayList<>();
        List<CutPiece> pieces = new ArrayList<>();
        JobCardItem item = new JobCardItem();
        CutPiece cutPiece = new CutPiece();
        pieces.add(cutPiece);
        item.setCutPieces(pieces);
        items.add(item);
        jobCard.setItems(items);
        return jobCard;
    }

    /*
     *  save card
     * */
    @Transactional( rollbackFor = Exception.class )
    public void save(JobCard jobCard) {
        if (jobCard != null && jobCard.getItems() != null) {
            jobCard.setInventoryStatus(String.valueOf(JobCard.InventoryStatus.NOT_RECEIVED_YET));
            long jobCardId = jobCardDAO.save( jobCard );
            jobCard.setId( jobCardId );
            generateCode( jobCard );
            jobCardDAO.save( jobCard );
            // save items
            List<CutPiece> cutPieces = new ArrayList<>();
            for (JobCardItem item : jobCard.getItems()) {
                item.setJobCardId(jobCardId);
                long itemId = jobCardItemDAO.save(item);
                for (CutPiece cutPiece : item.getCutPieces()) {
                    cutPiece.setJobCardItemId(itemId);
                    cutPieces.add(cutPiece);
                }
            }
            // save all pieces
            cutPieceDAO.saveAll(cutPieces);
        }
    }


    /*
    * generate code
    * */
    private void generateCode( JobCard card ){
        if( StringUtils.isNullOrEmpty( card.getCode() ) ){
            String code = "JC-" + card.getId() + "-" + card.getCreatedAt().toLocalDate().toString();
            card.setCode( code );
        }
    }

    /*
     *  find card recursively
     * */
    public JobCard findCardRecursively(long id) {
        // get username
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        JobCard jobCard = jobCardDAO.find(id);
        // set po and site titles
        jobCard.setPurchaseOrderTitle( purchaseOrderDAO.find( jobCard.getPurchaseOrderId() ).getCode() );
        jobCard.setLocationTitle( locationSiteDAO.find( jobCard.getLocationSiteId() ).getTitle() );

        // fetch only those items which has account id assign to user
        List<Long> accountIds = userInventoryAccountDAO.findByUsername( authentication.getName() )
                .stream()
                .map( UserInventoryAccount::getAccountId )
                .collect( Collectors.toList() );

        // get items has account ids and has actual production not filled
        List<JobCardItem> items = jobCardItemDAO.findByJobCardAndAccountIdsAndIsReceived( id, 0L ,accountIds );
        if (items != null && !items.isEmpty()) {
            // get job card ite ids
            List<Long> jobCardItemIds = items.stream()
                    .map(JobCardItem::getId)
                    .collect(Collectors.toList());

            // item Ids
            List<Long> invItemIds = items.stream()
                    .map(JobCardItem::getItemId)
                    .collect(Collectors.toList());

            Map<Long, List<CutPiece>> piecesMap = cutPieceDAO.findByJobCardItemIds( jobCardItemIds )
                    .stream()
                    .collect(Collectors.groupingBy( CutPiece::getJobCardItemId ));

            Map<Long, Item> itemMap = itemDAO.findByIds(invItemIds)
                    .stream()
                    .collect(Collectors.toMap(Item::getId, Function.identity()));

            for (JobCardItem item : items) {
                item.setCutPieces(piecesMap.getOrDefault(item.getId(), new ArrayList<>()));
                item.setTitle(itemMap.getOrDefault(item.getItemId(), new Item()).getTitle());
            }
        }
        jobCard.setItems(items);
        return jobCard;
    }

    /*
     *  find card recursively
     * */
    public JobCard findCardRecursivelyForView(long id) {
        // get username
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        JobCard jobCard = jobCardDAO.find(id);
        // set po and site titles
        jobCard.setPurchaseOrderTitle( purchaseOrderDAO.find( jobCard.getPurchaseOrderId() ).getCode() );
        jobCard.setLocationTitle( locationSiteDAO.find( jobCard.getLocationSiteId() ).getTitle() );
        // set items
        List<JobCardItem> items = jobCardItemDAO.findByCardId(id);
        if (items != null && !items.isEmpty()) {
            // get job card ite ids
            List<Long> jobCardItemIds = items.stream()
                    .map(JobCardItem::getId)
                    .collect(Collectors.toList());

            // item Ids
            List<Long> invItemIds = items.stream()
                    .map(JobCardItem::getItemId)
                    .collect(Collectors.toList());

            Map<Long, List<CutPiece>> piecesMap = cutPieceDAO.findByJobCardItemIds( jobCardItemIds )
                    .stream()
                    .collect(Collectors.groupingBy( CutPiece::getJobCardItemId ));

            Map<Long, Item> itemMap = itemDAO.findByIds(invItemIds)
                    .stream()
                    .collect(Collectors.toMap(Item::getId, Function.identity()));

            for (JobCardItem item : items) {
                item.setCutPieces(piecesMap.getOrDefault(item.getId(), new ArrayList<>()));
                item.setTitle(itemMap.getOrDefault(item.getItemId(), new Item()).getTitle());
            }
        }
        jobCard.setItems(items);
        return jobCard;
    }


    public String deleteJobCardItem(long jobCardItemId) {
        cutPieceDAO.deleteByItemId(jobCardItemId);
        jobCardItemDAO.delete(jobCardItemId);
        return "success";
    }

    public String deleteCutPieceItem( long cutPieceId ){
        cutPieceDAO.delete( cutPieceId );
        return "success";
    }


    public List<CutPieceType> getAllPieceTypes() {
        return cutPieceTypeDAO.findAll();
    }

    public List<JobCardItem> getJobCardItems( long jobCardId ){
        return jobCardItemDAO.findByCardId( jobCardId );
    }
    public JobCard findByID( long jobCardId ){
        return jobCardDAO.find( jobCardId );
    }

    public List<JobCardItem>  findJobCardItemByJobCardId( long jobCardId ){
       return jobCardItemDAO.findByCardId(jobCardId);
    }

    public List<CutPiece>  findCutPieceByJobCardItemIds( List<Long> itemIds  ){
        return cutPieceDAO.findByJobCardItemIdsWithGroupByType(itemIds);
    }

    public List<FinishedItem>  findFinishItemByJobCardId( long jobCardId ){
        return finishedItemDAO.findByJobCardId( jobCardId );
    }

    public List<StitchingOfflineItem>  findStitchItemByJobCardId( long jobCardId ){
        return stitchingOfflineItemDAO.findByJobCardId( jobCardId );
    }

    public Map<Long, Long> totalStitchingItem(List<Long> itemIds, long jobCardId ){
        return stitchingOfflineItemDAO.findTotalStitchingOfflineItems( itemIds, jobCardId );
    }

    public Map<Long, Long> totalFinishItem(List<Long> itemIds, long jobCardId ){
        return finishedItemDAO.findTotalFinishedItems( itemIds, jobCardId );
    }
}
