package com.utopiaindustries.service;

import com.utopiaindustries.dao.ctp.*;
import com.utopiaindustries.model.ctp.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
public class InventoryService {

    private final JobCardItemDAO jobCardItemDAO;
    private final CutPieceDAO cutPieceDAO;
    private final BundleDAO bundleDAO;
    private final InventoryTransactionLegDAO inventoryTransactionLegDAO;
    private final InventoryTransactionDAO inventoryTransactionDAO;
    private final JobCardDAO jobCardDAO;
    private final CryptographyService cryptographyService;
    private final MasterBundleDAO masterBundleDAO;
    private final FinishedItemDAO finishedItemDAO;
    private final StitchingOfflineItemDAO stitchingOfflineItemDAO;

    public InventoryService( JobCardItemDAO jobCardItemDAO, CutPieceDAO cutPieceDAO, BundleDAO bundleDAO, InventoryTransactionLegDAO inventoryTransactionLegDAO, InventoryTransactionDAO inventoryTransactionDAO, JobCardDAO jobCardDAO, CryptographyService cryptographyService, MasterBundleDAO masterBundleDAO, FinishedItemDAO finishedItemDAO, StitchingOfflineItemDAO stitchingOfflineItemDAO) {
        this.jobCardItemDAO = jobCardItemDAO;
        this.cutPieceDAO = cutPieceDAO;
        this.bundleDAO = bundleDAO;
        this.inventoryTransactionLegDAO = inventoryTransactionLegDAO;
        this.inventoryTransactionDAO = inventoryTransactionDAO;
        this.jobCardDAO = jobCardDAO;
        this.cryptographyService = cryptographyService;
        this.masterBundleDAO = masterBundleDAO;
        this.finishedItemDAO = finishedItemDAO;
        this.stitchingOfflineItemDAO = stitchingOfflineItemDAO;
    }

    private void updateJobCardInventoryStatus(   JobCard card ){
        List<JobCardItem> items = jobCardItemDAO.findByCardId(  card.getId( ) );
        for(  JobCardItem item :  items ){
            // check if item is received
            if(  item.getActualProduction( ) == null ||  item.getActualProduction( ).compareTo(  BigDecimal.ZERO ) == 0 ){
               return;
            }
        }
        card.setInventoryStatus(  JobCard.InventoryStatus.RECEIVED.name( ) );
        jobCardDAO.save(  card );
    }

    /*
     *  receive inv from job card
     * */
    @Transactional(  rollbackFor = Exception.class, propagation = Propagation.NESTED )
    public void receiveJobCardInventory(  long jobCardId, JobCardWrapper jobCardWrapper ) {
        if (  jobCardId == 0 || jobCardWrapper.getItems( ) == null || jobCardWrapper.getItems( ).isEmpty( ) ) {
            throw new RuntimeException( " JobCard can`t be empty");
        }
        JobCard jobCard = jobCardDAO.find( jobCardId );
        // get job cad items
        List<JobCardItemWrapper> jobCardItemWrappers = jobCardWrapper.getItems( );
        List<Long> jobCardItemWrapperIds = jobCardItemWrappers.stream( )
                .map(  JobCardItemWrapper::getJobCardItemId )
                .collect(  Collectors.toList( ) );

        Map<Long,BigDecimal> jobCardItemIdToActualProdMap = jobCardItemWrappers.stream( )
                .collect( Collectors.toMap(  JobCardItemWrapper::getJobCardItemId, JobCardItemWrapper::getActualProduction ) );

        List<JobCardItem> items = jobCardItemDAO.findByIds(  jobCardItemWrapperIds );
        if (  items != null && !items.isEmpty( ) ) {
            // get job card item ids
            List<Long> jobCardItemIds = items.stream( )
                    .map( JobCardItem::getId)
                    .collect( Collectors.toList( ));

            // save updated cut pieces
            List<CutPiece> cutPieces = jobCardItemWrappers.stream( )
                    .flatMap(  wrapper -> wrapper.getPieces( ).stream( ) )
                    .collect(  Collectors.toList( ) );
            cutPieceDAO.saveAll(  cutPieces );

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

            for (  JobCardItem jobCardItem : items) {
                // create + save bundles
                List<Bundle> bundles = createBundles(  jobCardItem, piecesMap.get(  jobCardItem.getId(  ) ) );
                // create transactions
                createTransactions(  bundles, jobCardItem.getAccountId( ), InventoryArtifactType.BUNDLE.name( ));
                jobCardItem.setActualProduction(  jobCardItemIdToActualProdMap.getOrDefault(  jobCardItem.getId( ) , BigDecimal.ZERO )  );
            }
            // update items with quantity
            jobCardItemDAO.saveAll(  items );
            // update job card inv status
            updateJobCardInventoryStatus(  jobCard );
        } else {
            throw new RuntimeException( "Items Not found in Job Card");
        }
    }

    /*
     * t create transactions
     * */
    private void createTransactions( List<? extends InventoryArtifact> artifacts, long accountId, String parentDocumentType) {
        if ( !artifacts.isEmpty( )) {
            InventoryTransaction transaction = createInventoryTransaction( "Transaction Against " + parentDocumentType);
            for ( InventoryArtifact artifact : artifacts) {
                InventoryTransactionLeg leg = createInventoryTransactionLeg( transaction, artifact, accountId, InventoryTransactionLeg.Type.IN.name( ), parentDocumentType );
            }
        }
    }


    /*
     * create and save transaction
     * */
    private InventoryTransaction createInventoryTransaction( String notes) {
        Authentication authentication = SecurityContextHolder.getContext( ).getAuthentication( );
        InventoryTransaction transaction = new InventoryTransaction( );
        transaction.setGeneratedBy( authentication.getName( ));
        transaction.setTransactionDateTime( LocalDateTime.now( ));
        transaction.setNotes( notes);
        transaction.setId( inventoryTransactionDAO.save( transaction));
        return transaction;
    }

    /*
     * create and save inventory transaction
     * */
    private InventoryTransactionLeg createInventoryTransactionLeg( InventoryTransaction transaction,
                                                                  InventoryArtifact artifact,
                                                                  long accountId,
                                                                  String transactionType,
                                                                  String parentDocumentType) {

        InventoryTransactionLeg inventoryTransactionLeg = new InventoryTransactionLeg( );
        inventoryTransactionLeg.setTransactionId( transaction.getId( ));
        inventoryTransactionLeg.setType( transactionType);
        inventoryTransactionLeg.setQuantity( BigDecimal.valueOf( 1L));
        inventoryTransactionLeg.setAccountId( ( int) accountId);
        inventoryTransactionLeg.setItemId( artifact.getItemId( ));
        inventoryTransactionLeg.setSku( artifact.getSku( ));
        inventoryTransactionLeg.setParentDocumentType( parentDocumentType);
        inventoryTransactionLeg.setParentDocumentId( artifact.getId( ));
        inventoryTransactionLeg.setParentDocumentPieceType( artifact.getType( ));
        inventoryTransactionLeg.setTransactionLegDateTime( LocalDateTime.now( ));
        // set balance
        BigDecimal initialBalance = calculateBalance( accountId, artifact.getItemId( ), parentDocumentType, artifact.getType( ));

        if ( transactionType.equalsIgnoreCase( InventoryTransactionLeg.Type.IN.name( ))) {
            initialBalance = initialBalance.add( inventoryTransactionLeg.getQuantity( ));
        }else if(transactionType.equalsIgnoreCase( InventoryTransactionLeg.Type.OUT.name( )) && inventoryTransactionLeg.getQuantity().equals(BigDecimal.ZERO)){
            initialBalance = BigDecimal.ZERO;
        } else {
            initialBalance = initialBalance.subtract( inventoryTransactionLeg.getQuantity( ));
        }
        inventoryTransactionLeg.setBalance( initialBalance);
        inventoryTransactionLeg.setId( inventoryTransactionLegDAO.save( inventoryTransactionLeg));
        return inventoryTransactionLeg;
    }


    // calculate balance
    private BigDecimal calculateBalance( long accountId, long itemId, String parentType, String pieceType) {
        // find the last transaction leg of the current account Id with itemId
        InventoryTransactionLeg lastTransactionLeg = inventoryTransactionLegDAO.findLastByAccountIdAndItemIdAndParentType( accountId, itemId, parentType, pieceType);
        if ( lastTransactionLeg.getBalance( ) != null) {
            return lastTransactionLeg.getBalance( );
        }
        return BigDecimal.ZERO;
    }


    // create bundles from cut pieces
    private List<Bundle> createBundles(  JobCardItem jobCardItem,
                                        List<CutPiece> jobCardItemPieces ) {
        Authentication authentication = SecurityContextHolder.getContext( ).getAuthentication( );
        if ( jobCardItemPieces != null && !jobCardItemPieces.isEmpty( )) {
            List<Bundle> bundles = new ArrayList<>( );
            // create bundle against every cut piece
            for ( CutPiece cutPiece : jobCardItemPieces ) {
                Bundle bundle = new Bundle( );
                bundle.setItemId(  jobCardItem.getItemId( ));
                bundle.setSku( jobCardItem.getSku( ));
                bundle.setJobCardId(  jobCardItem.getJobCardId( ) );
                bundle.setWrapQuantity( cutPiece.getQuantity( ));
                bundle.setType( cutPiece.getType( ));
                bundle.setCreatedAt( LocalDateTime.now( ));
                bundle.setCreatedBy( authentication.getName( ));
                bundles.add( bundle);
                bundle.setId( bundleDAO.save( bundle));
                bundle.setBarcode( cryptographyService.generateRandomString( 15));
                // save again after barcode generation
                bundle.setId( bundleDAO.save( bundle));
            }

//            for ( Map.Entry<JobCardItem, List<CutPiece>> entry : jobCardItemPieces ) {
//                JobCardItem key = entry.getKey( );
//                List<CutPiece> value = entry.getValue( );
//
//
//            }
            return bundles;
        }
        return new ArrayList<>( );
    }


    /*
     *  receive inventory from master barcode
     * */
    @Transactional( rollbackFor = Exception.class, propagation = Propagation.NESTED )
    public void receiveInventoryFromMasterBarcode( long masterId, long toAccount) {
        if ( masterId == 0 || toAccount == 0) {
            throw new RuntimeException( "Master Barcode | Account can`t be empty");
        }
        MasterBundle masterBundle = masterBundleDAO.find( masterId);
        // find bundles from master barcode
        List<Bundle> bundles = bundleDAO.findByMasterId( masterId);
        if ( bundles != null && !bundles.isEmpty( )) {
            // bundle ids
            List<Long> bundleIds = bundles.stream( ).map( Bundle::getId).collect( Collectors.toList( ));

            Map<Long, InventoryTransactionLeg> lastBundleIdInTransactionMap = inventoryTransactionLegDAO
                    .findLastTransactionByParentIdAndParentType( InventoryTransactionLeg.Type.IN.name( ), bundleIds, InventoryArtifactType.BUNDLE.name( ))
                    .stream( )
                    .collect( Collectors.toMap( InventoryTransactionLeg::getParentDocumentId, Function.identity( )));

            // create Transaction
            InventoryTransaction transaction = createInventoryTransaction( "Against Movement from Cutting to Stitching");
            inventoryTransactionDAO.save( transaction);
            // create transaction legs
            for ( Bundle bundle : bundles) {
                InventoryTransactionLeg lastInvTransaction = lastBundleIdInTransactionMap.getOrDefault( bundle.getId( ), null);
                if ( lastInvTransaction != null) {
                    // OUT
                    long fromAccount = lastInvTransaction.getAccountId( );
                    createInventoryTransactionLeg( transaction, bundle, fromAccount, InventoryTransactionLeg.Type.OUT.name( ), InventoryArtifactType.BUNDLE.name( ));
                    // IN
                    createInventoryTransactionLeg( transaction, bundle, toAccount, InventoryTransactionLeg.Type.IN.name( ), InventoryArtifactType.BUNDLE.name( ));
                }
            }
            // update status
            masterBundle.setIsReceived( true);
            masterBundleDAO.save( masterBundle);
        }
    }


    private void validateItems( List<JobCardItem> cardItems) {
        if ( cardItems == null || cardItems.isEmpty( )) {
            throw new RuntimeException( "Item cant be Empty | null");
        }
        for ( JobCardItem jobCardItem : cardItems) {
            if( jobCardItem.getTotalProduction() != null ){
                int finalQuantity = jobCardItem.getActualProduction( ).compareTo(  jobCardItem.getTotalProduction( ).add( jobCardItem.getProduction( )));
                if ( finalQuantity < 0) {
                    throw new RuntimeException( " Items cant be generated because it exceeds from limit of expected Production");
                }
            }
        }
    }

    private void checkAllBundleAreReceived( long jobCardId,
                                           List<JobCardItem> jobCardItems) {


        if ( jobCardItems != null && !jobCardItems.isEmpty( ) ) {
            List<Long> itemIds = jobCardItems.stream( )
                    .filter( JobCardItem::getIsSelected )
                    .map( JobCardItem::getItemId).collect( Collectors.toList( ));
            List<Bundle> bundles = bundleDAO.findByItemIdsAndCardId( itemIds, jobCardId);
            List<Long> masterBundleIds = bundles.stream( ).map( Bundle::getMasterBundleId).collect( Collectors.toList( ));
            
            Map<Long, MasterBundle> idToMasterBundleMap = masterBundleDAO.findByIds( masterBundleIds)
                    .stream( )
                    .collect( Collectors.toMap( MasterBundle::getId, Function.identity( )));

            for ( Bundle bundle : bundles) {
                // check all bundles are received
                MasterBundle masterBundle = idToMasterBundleMap.getOrDefault( bundle.getMasterBundleId( ), null);
                if ( masterBundle == null || ! masterBundle.getIsReceived( ) ) {
                    throw new RuntimeException( "Please Receive Bundles Against Master Bundles First to Create Finished Item");
                }
            }
        }
    }

    /*
     *  create finished items from master bundles
     * */
    @Transactional( rollbackFor = Exception.class, propagation = Propagation.NESTED )
    public void createStitchingOfflineItemsFromJobCard( JobCard jobCard) {
        List<JobCardItem> jobCardItems = jobCard.getItems( );
        List<JobCardItem> updatedItems = new ArrayList<>( );

        // validate items
        validateItems( jobCardItems);
        // check whether all bundles are received against finish goods
        checkAllBundleAreReceived( jobCard.getId( ), jobCardItems);
        for ( JobCardItem item : jobCardItems) {
            if(item.getTotalProduction() == null){
                item.setTotalProduction(BigDecimal.ZERO);
            }
            item.setJobCardId(jobCard.getId());
            // select which has inventory
            if (  item.getProduction( ).compareTo( BigDecimal.ZERO ) != 0 ) {
                // production is completed out bundles
                if (   item.getActualProduction( ).compareTo( item.getTotalProduction( ).add( item.getProduction( ))) == 0) {
                    // create out transactions of bundles in master bundles
                    List<Bundle> bundles = bundleDAO.findByItemIdAndCardId( item.getItemId( ), jobCard.getId( ));
                    if ( bundles != null && !bundles.isEmpty( )) {
                        // bundle ids
                        List<Long> bundleIds = bundles.stream( ).map( Bundle::getId).collect( Collectors.toList( ));

                        Map<Long, InventoryTransactionLeg> lastBundleIdInTransactionMap = inventoryTransactionLegDAO
                                .findLastTransactionByParentIdAndParentType( InventoryTransactionLeg.Type.IN.name( ), bundleIds, InventoryArtifactType.BUNDLE.name( ))
                                .stream( )
                                .collect( Collectors.toMap( InventoryTransactionLeg::getParentDocumentId, Function.identity( )));
                        // create Transaction
                        InventoryTransaction transaction = createInventoryTransaction( "Against Movement from Stitching to Stitched Offline Item");
                        inventoryTransactionDAO.save( transaction);
                        // create transaction legs
                        for ( Bundle bundle : bundles) {
                            InventoryTransactionLeg lastInvTransaction = lastBundleIdInTransactionMap.getOrDefault( bundle.getId( ), null);
                            if ( lastInvTransaction != null) {
                                // OUT
                                long fromAccount = lastInvTransaction.getAccountId( );
                                createInventoryTransactionLeg( transaction, bundle, fromAccount, InventoryTransactionLeg.Type.OUT.name( ), InventoryArtifactType.BUNDLE.name( ));
                            }
                        }
                    }
                }
                // create finished items
                List<StitchingOfflineItem> stitchingOfflineItems = createStitchingOfflineItems( item);
                // create IN Transactions of Finished Items into account
                createTransactions( stitchingOfflineItems, jobCard.getToAccountId( ), InventoryArtifactType.STITCHING_OFFLINE.name( ));
                item.setTotalProduction( item.getTotalProduction( ).add( item.getProduction( )));
                item.setJobCardId( jobCard.getId( ));
                updatedItems.add( item);
            }
        }
        // save all
        jobCardItemDAO.saveAll( updatedItems);
    }

    /*
     *  create finished items
     * */
    public List<StitchingOfflineItem> createStitchingOfflineItems( JobCardItem jobCardItem) {
        Authentication authentication = SecurityContextHolder.getContext( ).getAuthentication( );
        List<StitchingOfflineItem> items = new ArrayList<>( );
        if ( jobCardItem != null) {
            for ( int i = 1; i <= jobCardItem.getProduction( ).intValue( ); i++) {
                StitchingOfflineItem stitchingOfflineItem = new StitchingOfflineItem( );
                stitchingOfflineItem.setCreatedAt( LocalDateTime.now( ));
                stitchingOfflineItem.setCreatedBy( authentication.getName( ));
                stitchingOfflineItem.setItemId( jobCardItem.getItemId( ));
                stitchingOfflineItem.setSku( jobCardItem.getSku( ));
                stitchingOfflineItem.setJobCardId( jobCardItem.getJobCardId( ));
                stitchingOfflineItem.setIsQa( false );
                long id = stitchingOfflineItemDAO.save( stitchingOfflineItem);
                stitchingOfflineItem.setId( id);
                stitchingOfflineItem.setBarcode( cryptographyService.generateRandomString( 15));
                stitchingOfflineItemDAO.save( stitchingOfflineItem);
                items.add( stitchingOfflineItem);
            }
        }
        return items;
    }

    /*
     * find transactions by account id
     * */
    public List<InventoryTransactionLeg> findTransactionByAccountId( long accountId) {
        List<InventoryTransactionLeg> legs = inventoryTransactionLegDAO.findByAccountId( accountId);

        List<Long> tIds = legs.stream( ).map( InventoryTransactionLeg::getTransactionId).collect( Collectors.toList( ));
        Map<Long, InventoryTransaction> idToTransactioMap = inventoryTransactionDAO.findByIds( tIds).stream( ).collect( Collectors.toMap( InventoryTransaction::getId, Function.identity( )));
        legs.forEach( leg -> leg.setTransaction( idToTransactioMap.get( leg.getTransactionId( ))));
        return legs;
    }

    /*
     *
     * */
    @Transactional( rollbackFor = Exception.class)
    public void markFinishedItemsApproved( List<Long> fItemIds, long toAccount) {

        if ( fItemIds.isEmpty( ) || toAccount == 0) {
            throw new RuntimeException( "Finished Item Ids | Account can`t be empty");
        }

        List<FinishedItem> finishedItems = finishedItemDAO.findByIds( fItemIds);
        if ( finishedItems != null && !finishedItems.isEmpty( )) {
            // bundle ids
            List<Long> finishedItemIds = finishedItems.stream( ).map( FinishedItem::getId).collect( Collectors.toList( ));

            Map<Long, InventoryTransactionLeg> lastBundleIdInTransactionMap = inventoryTransactionLegDAO
                    .findLastTransactionByParentIdAndParentType( InventoryTransactionLeg.Type.IN.name( ), finishedItemIds, InventoryArtifactType.STITCHING_OFFLINE.name( ))
                    .stream( )
                    .collect( Collectors.toMap( InventoryTransactionLeg::getParentDocumentId, Function.identity( )));

            // create Transaction
            InventoryTransaction transaction = createInventoryTransaction( "Against Movement from Stitching to Packaging After QA");
            inventoryTransactionDAO.save( transaction);
            // create transaction legs
            for ( FinishedItem finishedItem : finishedItems) {
                InventoryTransactionLeg lastInvTransaction = lastBundleIdInTransactionMap.getOrDefault( finishedItem.getId( ), null);
                if ( lastInvTransaction != null) {
                    // OUT
                    long fromAccount = lastInvTransaction.getAccountId( );
                    createInventoryTransactionLeg( transaction, finishedItem, fromAccount, InventoryTransactionLeg.Type.OUT.name( ), InventoryArtifactType.STITCHING_OFFLINE.name( ));
                    // IN
                    createInventoryTransactionLeg( transaction, finishedItem, toAccount, InventoryTransactionLeg.Type.IN.name( ), InventoryArtifactType.STITCHING_OFFLINE.name( ));
                }
                finishedItem.setIsQa( true);
            }
            finishedItemDAO.saveAll( finishedItems);
        }
    }


    /*
     *  generate finished items against stitched items
     * */
    @Transactional( rollbackFor = Exception.class)
    public void createFinishedItemsAgainstStitchedItems(  StitchedItemWrapper wrapper ) {
        if ( wrapper.getItems( ) != null && wrapper.getFinishedAccountId( ) != 0) {
            Authentication authentication = SecurityContextHolder.getContext( ).getAuthentication( );

            List<StitchingOfflineItem> stitchingOfflineItems = wrapper.getItems( );
            List<StitchingOfflineItem> updatedStitchedItems = new ArrayList<>( );
            List<FinishedItem> finishedItems = new ArrayList<>( );

            List<Long> stitchedItemIds = stitchingOfflineItems.stream( )
                    .map( StitchingOfflineItem::getId)
                    .collect( Collectors.toList( ));

            Map<Long, InventoryTransactionLeg> lastStitchedIdInTransactionMap = inventoryTransactionLegDAO
                    .findLastTransactionByParentIdAndParentType( InventoryTransactionLeg.Type.IN.name( ), stitchedItemIds, InventoryArtifactType.STITCHING_OFFLINE.name( ))
                    .stream( )
                    .collect( Collectors.toMap( InventoryTransactionLeg::getParentDocumentId, Function.identity( )));

            // get finished items from stitched items if exists
            List<Long> preCreatedFinishedItemIds = finishedItemDAO.findByStitchedItemIds(  stitchedItemIds ).stream( ).
                    map(  FinishedItem::getId ).collect( Collectors.toList( ));

            Map<Long, InventoryTransactionLeg> lastFinishedItemInTransactionMap = inventoryTransactionLegDAO
                    .findLastTransactionByParentIdAndParentType( InventoryTransactionLeg.Type.IN.name( ), preCreatedFinishedItemIds, InventoryArtifactType.FINISHED_ITEM.name( ))
                    .stream( )
                    .collect( Collectors.toMap( InventoryTransactionLeg::getParentDocumentId, Function.identity( )));


            InventoryTransaction transaction = createInventoryTransaction( "Against Movement From Stitching Offline to Finished Item");

            // generate FI for SI
            for ( StitchingOfflineItem stitchingOfflineItem : stitchingOfflineItems) {
                if ( stitchingOfflineItem.getQaStatus( ).equalsIgnoreCase( "APPROVED" )) {
                    // check finished item is already created
                    FinishedItem preCreatedItem = finishedItemDAO.findByStitchedItem( stitchingOfflineItem.getId( ));
                    if ( preCreatedItem == null) {
                        FinishedItem finishedItem = new FinishedItem( );
                        finishedItem.setItemId( stitchingOfflineItem.getItemId( ));
                        finishedItem.setSku( stitchingOfflineItem.getSku( ));
                        finishedItem.setBarcode( stitchingOfflineItem.getBarcode( ));
                        finishedItem.setCreatedBy( authentication.getName( ));
                        finishedItem.setCreatedAt( LocalDateTime.now( ));
                        finishedItem.setStitchedItemId( stitchingOfflineItem.getId( ));
                        finishedItem.setJobCardId( stitchingOfflineItem.getJobCardId( ));
                        finishedItem.setIsQa( false);
                        finishedItem.setId( finishedItemDAO.save( finishedItem));
                        finishedItems.add( finishedItem);
                        InventoryTransactionLeg lastInvTransaction = lastStitchedIdInTransactionMap.getOrDefault( stitchingOfflineItem.getId( ), null);
                        if (  lastInvTransaction != null ) {
                            // OUT
                            long fromAccount = lastInvTransaction.getAccountId( );
                            createInventoryTransactionLeg( transaction, stitchingOfflineItem, fromAccount, InventoryTransactionLeg.Type.OUT.name( ), InventoryArtifactType.STITCHING_OFFLINE.name( ));
                        }
                        // update stitched item
                        stitchingOfflineItem.setIsQa( true);
                        // if FI is already created
                    } else {
                        // create OUT from stitching account Finished Item
                        InventoryTransactionLeg lastInvTransaction = lastFinishedItemInTransactionMap.getOrDefault(  preCreatedItem.getId( ), null);
                        if (  lastInvTransaction != null ) {
                            // OUT
                            long fromAccount = lastInvTransaction.getAccountId( );
                            createInventoryTransactionLeg( transaction, stitchingOfflineItem, fromAccount, InventoryTransactionLeg.Type.OUT.name( ), InventoryArtifactType.FINISHED_ITEM.name( ));
                        }
                        // create IN in finishing Account Finished Item
                        finishedItems.add(  preCreatedItem );
                    }
                }
            }
            for ( FinishedItem finishedItem : finishedItems) {
                // IN
                createInventoryTransactionLeg( transaction, finishedItem, wrapper.getFinishedAccountId( ), InventoryTransactionLeg.Type.IN.name( ), InventoryArtifactType.FINISHED_ITEM.name( ));
            }
            // save updated stitched items
            stitchingOfflineItemDAO.saveAll( wrapper.getItems( ));
        }
    }


    /*
     *   segregate finish items
     * */
    @Transactional(  rollbackFor = Exception.class, propagation = Propagation.NESTED )
    public void segregateFinishedItems( FinishedItemWrapper wrapper) {
        if ( wrapper != null && wrapper.getItems( ) != null) {

            List<FinishedItem> items = wrapper.getItems( );
            List<FinishedItem> updatedItems = new ArrayList<>( );
            // finished ids
            List<Long> finishedItemIds = items.stream( ).map( FinishedItem::getId)
                    .collect( Collectors.toList( ));
            // stitched ids
            List<Long> stitchedItemIds = items.stream( ).map( FinishedItem::getStitchedItemId)
                    .collect( Collectors.toList( ));

            // create parent doc type last IN transaction map
            Map<Long, InventoryTransactionLeg> lastFinishedItemIdInTransactionMap = inventoryTransactionLegDAO
                    .findLastTransactionByParentIdAndParentType( InventoryTransactionLeg.Type.IN.name( ), finishedItemIds, InventoryArtifactType.FINISHED_ITEM.name( ))
                    .stream( )
                    .collect( Collectors.toMap( InventoryTransactionLeg::getParentDocumentId, Function.identity( )));

            // create parent doc type last OUT transaction map
            Map<Long, InventoryTransactionLeg> lastStitchedItemOutTransactionMap = inventoryTransactionLegDAO
                    .findLastTransactionByParentIdAndParentType( InventoryTransactionLeg.Type.OUT.name( ), stitchedItemIds, InventoryArtifactType.STITCHING_OFFLINE.name( ))
                    .stream( )
                    .collect( Collectors.toMap( InventoryTransactionLeg::getParentDocumentId, Function.identity( )));

            // create transaction
            InventoryTransaction transaction = createInventoryTransaction( "Against Segregation of Finished Items");


            // create IN and OUT for all approved items
            for ( FinishedItem finishedItem : items) {
                InventoryTransactionLeg lastInvTransaction = lastFinishedItemIdInTransactionMap.getOrDefault( finishedItem.getId( ), null);
                finishedItem.setIsQa( true);
                /*
                 *  item is not approved and washed is selected then item remain in Finishing account
                 * */

                /*
                 *  item is approved and alter is selected then finished item will to stitching account
                 * */
                if ( finishedItem.getQaStatus( ).equalsIgnoreCase( "ALTER")) {
                    // create OUT and IN transactions for FI
                    if ( lastInvTransaction != null) {
                        // OUT
                        long fromAccount = lastInvTransaction.getAccountId( );
                        createInventoryTransactionLeg( transaction, finishedItem, fromAccount, InventoryTransactionLeg.Type.OUT.name( ), InventoryArtifactType.FINISHED_ITEM.name( ));
                        // IN
                        // get the stitching account id
                        long stitchedItemId = finishedItem.getStitchedItemId( );
                        InventoryTransactionLeg lastOutTransaction = lastStitchedItemOutTransactionMap.getOrDefault( stitchedItemId, null);
                        createInventoryTransactionLeg( transaction, finishedItem, lastOutTransaction.getAccountId( ), InventoryTransactionLeg.Type.IN.name( ), InventoryArtifactType.FINISHED_ITEM.name( ));
                    }
                }

                /*
                 * item is approved and grade is selected then fI is move to grade account
                 */
                if ( finishedItem.getQaStatus( ).equalsIgnoreCase( "APPROVED") && finishedItem.getAccountId( ) != 0) {
                    // create OUT and IN transactions for FI
                    if ( lastInvTransaction != null) {
                        // OUT
                        long fromAccount = lastInvTransaction.getAccountId( );
                        createInventoryTransactionLeg( transaction, finishedItem, fromAccount, InventoryTransactionLeg.Type.OUT.name( ), InventoryArtifactType.FINISHED_ITEM.name( ));
                        // IN
                        createInventoryTransactionLeg( transaction, finishedItem, finishedItem.getAccountId( ), InventoryTransactionLeg.Type.IN.name( ), InventoryArtifactType.FINISHED_ITEM.name( ));
                    }
                    finishedItem.setIsSegregated( true);
                }
                updatedItems.add( finishedItem);
            }
            finishedItemDAO.saveAll( updatedItems);
            // save finish items
        }
    }

    /*
     * find item summary by account
     * */
    public List<InventorySummary> findItemSummaryByAccountId( long accountId) {
        return inventoryTransactionLegDAO.findSummaryByAccountId( accountId);
    }
}
