package vn.azteam.tpf.service.impl;

import vn.azteam.tpf.domain.TBCrop;
import vn.azteam.tpf.domain.TBProductStockUpdate;
import vn.azteam.tpf.repository.TBCropRepository;
import vn.azteam.tpf.service.*;
import vn.azteam.tpf.Constants.Constants.PRODUCT_STOCK_UPDATE_ACTION;
import vn.azteam.tpf.domain.TBProductBlockDetails;
import vn.azteam.tpf.repository.TBProductBlockDetailsRepository;
import vn.azteam.tpf.repository.TBProductStockUpdateRepository;
import vn.azteam.tpf.repository.search.TBProductBlockDetailsSearchRepository;
import vn.azteam.tpf.service.dto.*;
import vn.azteam.tpf.service.mapper.TBProductBlockDetailsMapper;
import vn.azteam.tpf.service.util.RandomUtil;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static org.elasticsearch.index.query.QueryBuilders.*;

/**
 * Service Implementation for managing TBProductBlockDetails.
 */
@Service
@Transactional
public class TBProductBlockDetailsServiceImpl implements TBProductBlockDetailsService {

    private final int codeLength = 8;

    private final Logger log = LoggerFactory.getLogger(TBProductBlockDetailsServiceImpl.class);

    private final TBProductBlockDetailsRepository tBProductBlockDetailsRepository;

    private final TBProductBlockDetailsReportService tbProductBlockDetailsReportService;

    private final TBProductBlockDetailsMapper tBProductBlockDetailsMapper;

    private final TBProductBlockDetailsSearchRepository tBProductBlockDetailsSearchRepository;

    private final TBProductStockUpdateService tbProductStockUpdateService;

    private final UserService userService;

    private final TBActivityService tbActivityService;

    private final TBProductStockUpdateRepository tbProductStockUpdateRepository;

    private final TBCropRepository tbCropRepository;

    public TBProductBlockDetailsServiceImpl(TBProductBlockDetailsRepository tBProductBlockDetailsRepository,
                                            TBProductBlockDetailsReportService tbProductBlockDetailsReportService,
                                            TBProductBlockDetailsMapper tBProductBlockDetailsMapper,
                                            TBProductBlockDetailsSearchRepository tBProductBlockDetailsSearchRepository,
                                            TBProductStockUpdateService tbProductStockUpdateService,
                                            UserService userService,
                                            TBActivityService tbActivityService,
                                            TBProductStockUpdateRepository tbProductStockUpdateRepository, TBCropRepository tbCropRepository) {
        this.tBProductBlockDetailsRepository = tBProductBlockDetailsRepository;
        this.tbProductBlockDetailsReportService = tbProductBlockDetailsReportService;
        this.tBProductBlockDetailsMapper = tBProductBlockDetailsMapper;
        this.tBProductBlockDetailsSearchRepository = tBProductBlockDetailsSearchRepository;
        this.tbProductStockUpdateService = tbProductStockUpdateService;
        this.userService = userService;
        this.tbActivityService = tbActivityService;
        this.tbProductStockUpdateRepository = tbProductStockUpdateRepository;
        this.tbCropRepository = tbCropRepository;
    }

    /**
     * Save a tBProductBlockDetails.
     *
     * @param tBProductBlockDetailsDTO the entity to save
     * @return the persisted entity
     */
    @Override
    public TBProductBlockDetailsDTO save(TBProductBlockDetailsDTO tBProductBlockDetailsDTO) {
        log.debug("Request to save TBProductBlockDetails : {}", tBProductBlockDetailsDTO);

        TBProductBlockDetails tBProductBlockDetails = tBProductBlockDetailsMapper.toEntity(tBProductBlockDetailsDTO);
        tBProductBlockDetails = tBProductBlockDetailsRepository.save(tBProductBlockDetails);
        TBProductBlockDetailsDTO result = tBProductBlockDetailsMapper.toDto(tBProductBlockDetails);
        tBProductBlockDetailsSearchRepository.save(tBProductBlockDetails);
        return result;
    }

    /**
     * Get all the tBProductBlockDetails.
     *
     * @param pageable the pagination information
     * @return the list of entities
     */
    @Override
    @Transactional(readOnly = true)
    public Page<TBProductBlockDetailsDTO> findAll(Pageable pageable) {
        log.debug("Request to get all TBProductBlockDetails");
        return tBProductBlockDetailsRepository.findAll(pageable)
            .map(tBProductBlockDetailsMapper::toDto);
    }


    /**
     * Get one tBProductBlockDetails by id.
     *
     * @param id the id of the entity
     * @return the entity
     */
    @Override
    @Transactional(readOnly = true)
    public Optional<TBProductBlockDetailsDTO> findOne(Long id) {
        log.debug("Request to get TBProductBlockDetails : {}", id);
        return tBProductBlockDetailsRepository.findById(id)
            .map(tBProductBlockDetailsMapper::toDto);
    }

    /**
     * Delete the tBProductBlockDetails by id.
     *
     * @param id the id of the entity
     */
    @Override
    public void delete(Long id) {
        log.debug("Request to delete TBProductBlockDetails : {}", id);
        tbProductStockUpdateRepository.deleteByTbProductBlockDetailsId(id);
        tBProductBlockDetailsRepository.deleteById(id);
        tBProductBlockDetailsSearchRepository.deleteById(id);
    }

    /**
     * Search for the tBProductBlockDetails corresponding to the query.
     *
     * @param query    the query of the search
     * @param pageable the pagination information
     * @return the list of entities
     */
    @Override
    @Transactional(readOnly = true)
    public Page<TBProductBlockDetailsDTO> search(String query, Pageable pageable) {
        log.debug("Request to search for a page of TBProductBlockDetails for query {}", query);
        return tBProductBlockDetailsSearchRepository.search(queryStringQuery(query), pageable)
            .map(tBProductBlockDetailsMapper::toDto);
    }

    public void updateQuantityForProductBlockDetail(Long id) {
        List<TBProductStockUpdate> tbProductStockUpdates = tbProductStockUpdateRepository.findByTbProductBlockDetailsId(id);
        Double sum_action_0 = tbProductStockUpdates.stream()
            .filter(item -> item.getAction() == PRODUCT_STOCK_UPDATE_ACTION.IMPORT.getValue()
                && item.getTbProductBlockDetails() != null)
            .distinct()
            .mapToDouble(o -> o.getQuantity()).sum();

        Double sum_action_1 = tbProductStockUpdates.stream()
            .filter(item -> item.getAction() == PRODUCT_STOCK_UPDATE_ACTION.EXPORT.getValue()
                && item.getTbProductBlockDetails() != null)
            .distinct()
            .mapToDouble(o -> o.getQuantity()).sum();

        Double total = sum_action_0 - sum_action_1;
        TBProductBlockDetailsDTO tbProductBlockDetailsDTO = findOne(id).get();
        tbProductBlockDetailsDTO.setQuantity(total);
        TBProductBlockDetailsDTO result = save(tbProductBlockDetailsDTO);
        this.createProductBlockDetailReport(result);
    }

    // Using for report
    public void createProductBlockDetailReport(TBProductBlockDetailsDTO tbProductBlockDetailsDTO) {
        Long tbCropId = tbProductBlockDetailsDTO.getTbCropId();
        if (tbCropId != null) {
            // Find all TBProductBlockDetails by tbCropId
            List<TBProductBlockDetails> tbProductBlockDetails = tBProductBlockDetailsRepository.findByTbCropId(tbCropId);
            List<Long> ids = new ArrayList<>();
            if (tbProductBlockDetails != null && tbProductBlockDetails.size() > 0) {
                tbProductBlockDetails.forEach(item -> ids.add(item.getId()));
                List<TBProductStockUpdate> tbProductStockUpdates = tbProductStockUpdateRepository.findByTbProductBlockDetailsIdIn(ids);
                Double productLevel1In = 0.0;
                Double productLevel1Out = 0.0;
                Double productLevel2In = 0.0;
                Double productLevel2Out = 0.0;
                Double productLevel3In = 0.0;
                Double productLevel3Out = 0.0;
                if (tbProductStockUpdates != null && tbProductStockUpdates.size() > 0) {
                    productLevel1In = tbProductStockUpdates.stream()
                        .filter(item -> item.getAction() == PRODUCT_STOCK_UPDATE_ACTION.IMPORT.getValue()
                            && item.getTbProductBlockDetails() != null
                            && item.getTbProductBlockDetails().getProductLevel() != null
                            && item.getTbProductBlockDetails().getProductLevel() == 1)
                        .distinct()
                        .mapToDouble(o -> o.getQuantity()).sum();
                    productLevel1Out = tbProductStockUpdates.stream()
                        .filter(item -> item.getAction() == PRODUCT_STOCK_UPDATE_ACTION.EXPORT.getValue()
                            && item.getTbProductBlockDetails() != null
                            && item.getTbProductBlockDetails().getProductLevel() != null
                            && item.getTbProductBlockDetails().getProductLevel() == 1)
                        .distinct()
                        .mapToDouble(o -> o.getQuantity()).sum();

                    productLevel2In = tbProductStockUpdates.stream()
                        .filter(item -> item.getAction() == PRODUCT_STOCK_UPDATE_ACTION.IMPORT.getValue()
                            && item.getTbProductBlockDetails() != null
                            && item.getTbProductBlockDetails().getProductLevel() != null
                            && item.getTbProductBlockDetails().getProductLevel() == 2)
                        .distinct()
                        .mapToDouble(o -> o.getQuantity()).sum();
                    productLevel2Out = tbProductStockUpdates.stream()
                        .filter(item -> item.getAction() == PRODUCT_STOCK_UPDATE_ACTION.EXPORT.getValue()
                            && item.getTbProductBlockDetails() != null
                            && item.getTbProductBlockDetails().getProductLevel() != null
                            && item.getTbProductBlockDetails().getProductLevel() == 2)
                        .distinct()
                        .mapToDouble(o -> o.getQuantity()).sum();

                    productLevel3In = tbProductStockUpdates.stream()
                        .filter(item -> item.getAction() == PRODUCT_STOCK_UPDATE_ACTION.IMPORT.getValue()
                            && item.getTbProductBlockDetails() != null
                            && item.getTbProductBlockDetails().getProductLevel() != null
                            && item.getTbProductBlockDetails().getProductLevel() == 3)
                        .distinct()
                        .mapToDouble(o -> o.getQuantity()).sum();
                    productLevel3Out = tbProductStockUpdates.stream()
                        .filter(item -> item.getAction() == PRODUCT_STOCK_UPDATE_ACTION.EXPORT.getValue()
                            && item.getTbProductBlockDetails() != null
                            && item.getTbProductBlockDetails().getProductLevel() != null
                            && item.getTbProductBlockDetails().getProductLevel() == 3)
                        .distinct()
                        .mapToDouble(o -> o.getQuantity()).sum();
                }
                Double productLevel1 = productLevel1In - productLevel1Out;
                Double productLevel2 = productLevel2In - productLevel2Out;
                Double productLevel3 = productLevel3In - productLevel3Out;
                TBProductBlockDetailsReportDTO tbProductBlockDetailsReportDTO = new TBProductBlockDetailsReportDTO();
                tbProductBlockDetailsReportDTO.setTbProductBlockDetailsId(tbProductBlockDetailsDTO.getId());
                tbProductBlockDetailsReportDTO.setTbCropId(tbProductBlockDetailsDTO.getTbCropId());
                TBCrop tbCrop = tbCropRepository.getOne(tbProductBlockDetailsDTO.getTbCropId());
                tbProductBlockDetailsReportDTO.setCode(tbCrop.getCode());
                tbProductBlockDetailsReportDTO.setProductLevel1(productLevel1);
                tbProductBlockDetailsReportDTO.setProductLevel2(productLevel2);
                tbProductBlockDetailsReportDTO.setProductLevel3(productLevel3);
                tbProductBlockDetailsReportDTO.setCreateDate(Instant.now());
                tbProductBlockDetailsReportService.save(tbProductBlockDetailsReportDTO);
            }
        }
    }

    public TBProductBlockDetailsDTO createProductBlockUpdate(ProductBlockUpdateDTO productBlockUpdateDTO) {
        Optional<UserDTO> currentUser = userService.getCurrentUserDTO();

        if (productBlockUpdateDTO.getAction() == PRODUCT_STOCK_UPDATE_ACTION.EXPORT.getValue() && productBlockUpdateDTO.getProductBlockDetailsDTO() != null) {
            TBProductStockUpdateDTO tbProductStockUpdateDTO = new TBProductStockUpdateDTO();
            tbProductStockUpdateDTO.setUpdateDate(Instant.now());
            tbProductStockUpdateDTO.setAction(productBlockUpdateDTO.getAction());
            tbProductStockUpdateDTO.setQuantity(productBlockUpdateDTO.getQuantity());
            tbProductStockUpdateDTO.setDescription(productBlockUpdateDTO.getDescription());

            if (currentUser.isPresent()) {
                tbProductStockUpdateDTO.setUpdateBy(currentUser.get().getId());
            }

            tbProductStockUpdateDTO.setTbProductBlockDetailsId(productBlockUpdateDTO.getProductBlockDetailsId());
            tbProductStockUpdateService.save(tbProductStockUpdateDTO);
            this.updateQuantityForProductBlockDetail(productBlockUpdateDTO.getProductBlockDetailsId());

            return productBlockUpdateDTO.getProductBlockDetailsDTO();
        } else if (productBlockUpdateDTO.getAction() == PRODUCT_STOCK_UPDATE_ACTION.IMPORT.getValue()) {
            TBProductBlockDetailsDTO tbProductBlockDetailsDTO = new TBProductBlockDetailsDTO();
            String code;
            do {
                code = RandomUtil.generateCode(this.codeLength);
            } while (tBProductBlockDetailsRepository.existsByCode(code));
            tbProductBlockDetailsDTO.setCode(code.toUpperCase());
            tbProductBlockDetailsDTO.setQuantity(productBlockUpdateDTO.getQuantity());
            tbProductBlockDetailsDTO.setHarvestDate(productBlockUpdateDTO.getHarvestDate());
            tbProductBlockDetailsDTO.setExpDate(productBlockUpdateDTO.getExpDate());
            tbProductBlockDetailsDTO.setProductLevel(productBlockUpdateDTO.getProductLevel());
            tbProductBlockDetailsDTO.setTbProductUnitId(productBlockUpdateDTO.getProductUnitId());
            tbProductBlockDetailsDTO.setTbCropId(productBlockUpdateDTO.getCropId());
            tbProductBlockDetailsDTO.setTbEntityId(productBlockUpdateDTO.getEntityId());
            TBProductBlockDetailsDTO result = save(tbProductBlockDetailsDTO);

            TBActivityDTO tbActivityDTO = tbActivityService.getHarvestActivityInstance();
            tbActivityDTO.setCropId(productBlockUpdateDTO.getCropId());
            tbActivityDTO.setCreatedById(currentUser.get().getId());
            tbActivityDTO.setDescription(productBlockUpdateDTO.getDescription());
            TBActivityDTO activityDTO = tbActivityService.save(tbActivityDTO);

            TBProductStockUpdateDTO tbProductStockUpdateDTO = new TBProductStockUpdateDTO();
            tbProductStockUpdateDTO.setTbActivityId(activityDTO.getId());
            tbProductStockUpdateDTO.setUpdateDate(Instant.now());
            tbProductStockUpdateDTO.setAction(productBlockUpdateDTO.getAction());
            tbProductStockUpdateDTO.setQuantity(productBlockUpdateDTO.getQuantity());
            tbProductStockUpdateDTO.setDescription(productBlockUpdateDTO.getDescription());

            if (currentUser.isPresent()) {
                tbProductStockUpdateDTO.setUpdateBy(currentUser.get().getId());
            }

            tbProductStockUpdateDTO.setTbProductBlockDetailsId(result.getId());
            tbProductStockUpdateService.save(tbProductStockUpdateDTO);
            this.createProductBlockDetailReport(result);
            return result;
        } else {
            return null;
        }
    }
}
