package vn.azteam.tpf.service;

import java.time.Instant;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.persistence.criteria.JoinType;

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

import io.github.jhipster.service.QueryService;

import vn.azteam.tpf.domain.TBSuppliesInWarehouse;
import vn.azteam.tpf.domain.*; // for static metamodels
import vn.azteam.tpf.repository.TBStockUpdateRepository;
import vn.azteam.tpf.repository.TBSuppliesInWarehouseRepository;
import vn.azteam.tpf.repository.TBSuppliesTypeRepository;
import vn.azteam.tpf.repository.search.TBSuppliesInWarehouseSearchRepository;
import vn.azteam.tpf.service.dto.*;
import vn.azteam.tpf.service.mapper.TBSuppliesInWarehouseMapper;

import static vn.azteam.tpf.Constants.Constants.ENUM_SUPPLIES_TYPE.GIONG;

import vn.azteam.tpf.service.util.Unit;
import vn.azteam.tpf.web.rest.errors.BadRequestAlertException;

import static vn.azteam.tpf.web.rest.util.ConvertCharacterUtil.removeAccent;

/**
 * Service for executing complex queries for TBSuppliesInWarehouse entities in the database.
 * The main input is a {@link TBSuppliesInWarehouseCriteria} which gets converted to {@link Specification},
 * in a way that all the filters must apply.
 * It returns a {@link List} of {@link TBSuppliesInWarehouseDTO} or a {@link Page} of {@link TBSuppliesInWarehouseDTO} which fulfills the criteria.
 */
@Service
@Transactional(readOnly = true)
public class TBSuppliesInWarehouseQueryService extends QueryService<TBSuppliesInWarehouse> {

    private static final String ENTITY_NAME = "tBSuppliesInWarehouse";

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

    private final TBSuppliesInWarehouseRepository tBSuppliesInWarehouseRepository;

    private final TBSuppliesInWarehouseMapper tBSuppliesInWarehouseMapper;

    private final TBSuppliesInWarehouseSearchRepository tBSuppliesInWarehouseSearchRepository;

    private final TBSuppliesInWarehouseService tBSuppliesInWarehouseService;

    private final UserService userService;

    private final TBStockUpdateService tBStockUpdateService;

    private final TBStockUpdateRepository tBStockUpdateRepository;

    private final TBSuppliesService tBSuppliesService;

    private final TBSuppliesTypeRepository tBSuppliesTypeRepository;

    private final TBBlockDetailsService tbBlockDetailsService;

    private final TBBlockDetailsReportService tbBlockDetailsReportService;

    public TBSuppliesInWarehouseQueryService(TBSuppliesInWarehouseRepository tBSuppliesInWarehouseRepository,
                                             TBSuppliesInWarehouseMapper tBSuppliesInWarehouseMapper,
                                             TBSuppliesInWarehouseSearchRepository tBSuppliesInWarehouseSearchRepository,
                                             TBSuppliesInWarehouseService tBSuppliesInWarehouseService,
                                             UserService userService,
                                             TBStockUpdateService tBStockUpdateService,
                                             TBStockUpdateRepository tBStockUpdateRepository,
                                             TBSuppliesService tBSuppliesService,
                                             TBSuppliesTypeRepository tBSuppliesTypeRepository, TBBlockDetailsService tbBlockDetailsService,
                                             TBBlockDetailsReportService tbBlockDetailsReportService) {
        this.tBSuppliesInWarehouseRepository = tBSuppliesInWarehouseRepository;
        this.tBSuppliesInWarehouseMapper = tBSuppliesInWarehouseMapper;
        this.tBSuppliesInWarehouseSearchRepository = tBSuppliesInWarehouseSearchRepository;
        this.tBSuppliesInWarehouseService = tBSuppliesInWarehouseService;
        this.userService = userService;
        this.tBStockUpdateService = tBStockUpdateService;
        this.tBStockUpdateRepository = tBStockUpdateRepository;
        this.tBSuppliesService = tBSuppliesService;
        this.tBSuppliesTypeRepository = tBSuppliesTypeRepository;
        this.tbBlockDetailsService = tbBlockDetailsService;
        this.tbBlockDetailsReportService = tbBlockDetailsReportService;
    }

    /**
     * Return a {@link List} of {@link TBSuppliesInWarehouseDTO} which matches the criteria from the database
     *
     * @param criteria The object which holds all the filters, which the entities should match.
     * @return the matching entities.
     */
    @Transactional(readOnly = true)
    public List<TBSuppliesInWarehouseDTO> findByCriteria(TBSuppliesInWarehouseCriteria criteria) {
        log.debug("find by criteria : {}", criteria);
        final Specification<TBSuppliesInWarehouse> specification = createSpecification(criteria);
        return tBSuppliesInWarehouseMapper.toDto(tBSuppliesInWarehouseRepository.findAll(specification));
    }

    /**
     * Return a {@link Page} of {@link TBSuppliesInWarehouseDTO} which matches the criteria from the database
     *
     * @param criteria The object which holds all the filters, which the entities should match.
     * @param page     The page, which should be returned.
     * @return the matching entities.
     */
    @Transactional(readOnly = true)
    public Page<TBSuppliesInWarehouseDTO> findByCriteria(TBSuppliesInWarehouseCriteria criteria, Pageable page) {
        log.debug("find by criteria : {}, page: {}", criteria, page);
        final Specification<TBSuppliesInWarehouse> specification = createSpecification(criteria);
        return tBSuppliesInWarehouseRepository.findAll(specification, page)
            .map(tBSuppliesInWarehouseMapper::toDto);
    }

    /**
     * Return the number of matching entities in the database
     *
     * @param criteria The object which holds all the filters, which the entities should match.
     * @return the number of matching entities.
     */
    @Transactional(readOnly = true)
    public long countByCriteria(TBSuppliesInWarehouseCriteria criteria) {
        log.debug("count by criteria : {}", criteria);
        final Specification<TBSuppliesInWarehouse> specification = createSpecification(criteria);
        return tBSuppliesInWarehouseRepository.count(specification);
    }

    /**
     * Function to convert TBSuppliesInWarehouseCriteria to a {@link Specification}
     */
    private Specification<TBSuppliesInWarehouse> createSpecification(TBSuppliesInWarehouseCriteria criteria) {
        Specification<TBSuppliesInWarehouse> specification = Specification.where(null);
        if (criteria != null) {
            if (criteria.getId() != null) {
                specification = specification.and(buildSpecification(criteria.getId(), TBSuppliesInWarehouse_.id));
            }
            if (criteria.getQuantity() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getQuantity(), TBSuppliesInWarehouse_.quantity));
            }
            if (criteria.getStatus() != null) {
                specification = specification.and(buildStringSpecification(criteria.getStatus(), TBSuppliesInWarehouse_.status));
            }
            if (criteria.getDescription() != null) {
                specification = specification.and(buildStringSpecification(criteria.getDescription(), TBSuppliesInWarehouse_.description));
            }
            if (criteria.getCreatedDate() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getCreatedDate(), TBSuppliesInWarehouse_.createdDate));
            }
            if (criteria.getModifiedDate() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getModifiedDate(), TBSuppliesInWarehouse_.modifiedDate));
            }
            if (criteria.getDeletedDate() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getDeletedDate(), TBSuppliesInWarehouse_.deletedDate));
            }
            if (criteria.getTbEntityId() != null) {
                specification = specification.and(buildSpecification(criteria.getTbEntityId(),
                    root -> root.join(TBSuppliesInWarehouse_.tbEntity, JoinType.LEFT).get(TBEntity_.id)));
            }
            if (criteria.getTbSuppliesId() != null) {
                specification = specification.and(buildSpecification(criteria.getTbSuppliesId(),
                    root -> root.join(TBSuppliesInWarehouse_.tbSupplies, JoinType.LEFT).get(TBSupplies_.id)));
            }
            if (criteria.getCreatedById() != null) {
                specification = specification.and(buildSpecification(criteria.getCreatedById(),
                    root -> root.join(TBSuppliesInWarehouse_.createdBy, JoinType.LEFT).get(TBDetailUser_.id)));
            }
            if (criteria.getModifiedById() != null) {
                specification = specification.and(buildSpecification(criteria.getModifiedById(),
                    root -> root.join(TBSuppliesInWarehouse_.modifiedBy, JoinType.LEFT).get(TBDetailUser_.id)));
            }
            if (criteria.getDeletedById() != null) {
                specification = specification.and(buildSpecification(criteria.getDeletedById(),
                    root -> root.join(TBSuppliesInWarehouse_.deletedBy, JoinType.LEFT).get(TBDetailUser_.id)));
            }
        }
        return specification;
    }

    @Transactional
    public TBSuppliesInWarehouseDTO createWarehouseStockUpdate(WarehouseStockUpdateDTO warehouseStockUpdateDTO) {
        //Get or create new TBSuppliesInWarehouse by warehouse_id and supply_Id
        TBSuppliesInWarehouseDTO tBSuppliesInWarehouseDTO = getTBSuppliesInWarehouseByWarehouseIdAndSupplyId(warehouseStockUpdateDTO.getTbWarehouseId(), warehouseStockUpdateDTO.getTbSuppliesId());

        //Get or create new TBBlockDetails by supply_in_warehouse_id and code
        TBBlockDetailsDTO tbBlockDetailsDTO = tbBlockDetailsService.getTBBlockDetailsByWarehouseStockUpdate(
            warehouseStockUpdateDTO, tBSuppliesInWarehouseDTO
        );

        if (warehouseStockUpdateDTO.getAction() == 1 &&
            tbBlockDetailsDTO.getQuantity() < warehouseStockUpdateDTO.getQuantity()) {
            throw new BadRequestAlertException("1014", "tBSuppliesInWarehouse", "1014");
        }

        //Create stock update
        TBStockUpdateDTO tbStockUpdateDTO = new TBStockUpdateDTO();
        tbStockUpdateDTO.setDescription(warehouseStockUpdateDTO.getDescription());
        tbStockUpdateDTO.setAction(warehouseStockUpdateDTO.getAction());
        tbStockUpdateDTO.setQuantity(warehouseStockUpdateDTO.getQuantity());
        tbStockUpdateDTO.setUpdateDate(warehouseStockUpdateDTO.getUpdateDate() == null ? Instant.now() : warehouseStockUpdateDTO.getUpdateDate());
        tbStockUpdateDTO.setTbSuppliesInWarehouseId(tBSuppliesInWarehouseDTO.getId());
        tbStockUpdateDTO.setTbBlockDetailsId(tbBlockDetailsDTO.getId());
        tBStockUpdateService.save(tbStockUpdateDTO);

        //Update quantity supplies in warehouse
        updateQuantityForTBSuppliesInWarehouseDTO(tBSuppliesInWarehouseDTO.getId());

        //Update quantity block details
        updateQuantityForTBBlockDetailsDTO(tBSuppliesInWarehouseDTO.getId(), tbBlockDetailsDTO.getId(), tBSuppliesInWarehouseDTO.getTbSuppliesId());

        return tBSuppliesInWarehouseDTO;
    }

    @Transactional(readOnly = false)
    public TBSuppliesInWarehouseDTO getTBSuppliesInWarehouseByWarehouseIdAndSupplyId(Long tbEntityId, Long tbSuppliesId) {
        UserDTO currentUser = userService.getCurrentUserDTO().get();
        List<TBSuppliesInWarehouse> data = tBSuppliesInWarehouseRepository.findAll().stream()
            .filter(item -> item.getTbSupplies() != null
                && item.getTbEntity() != null
                && item.getTbSupplies().getId() == tbSuppliesId
                && item.getTbEntity().getId() == tbEntityId)
            .distinct()
            .collect(Collectors.toList());

        if (data.size() > 0) {
            return tBSuppliesInWarehouseMapper.toDto(data).get(0);
        } else {
            TBSuppliesInWarehouseDTO tbSuppliesInWarehouseDTO = new TBSuppliesInWarehouseDTO();
            tbSuppliesInWarehouseDTO.setTbSuppliesId(tbSuppliesId);
            tbSuppliesInWarehouseDTO.setTbEntityId(tbEntityId);
            tbSuppliesInWarehouseDTO.setCreatedById(currentUser.getId());
            tbSuppliesInWarehouseDTO.setCreatedDate(Instant.now());
            tbSuppliesInWarehouseDTO.setQuantity(0.0);
            TBSuppliesInWarehouseDTO result = tBSuppliesInWarehouseService.save(tbSuppliesInWarehouseDTO);
            return result;
        }
    }

    @Transactional
    public void updateQuantityForTBSuppliesInWarehouseDTO(Long id) {
        List<TBStockUpdate> tbStockUpdates = tBStockUpdateRepository.findByTbSuppliesInWarehouseId(id);
        Double sum_action_0 = tbStockUpdates.stream()
            .filter(item -> item.getAction() == 0
                && item.getTbSuppliesInWarehouse() != null)
            .distinct()
            .mapToDouble(o -> o.getQuantity()).sum();

        Double sum_action_1 = tbStockUpdates.stream()
            .filter(item -> item.getAction() == 1
                && item.getTbSuppliesInWarehouse() != null)
            .distinct()
            .mapToDouble(o -> o.getQuantity()).sum();

        Double sum_action_2 = tbStockUpdates.stream()
            .filter(item -> item.getAction() == 2
                && item.getTbSuppliesInWarehouse() != null)
            .distinct()
            .mapToDouble(o -> o.getQuantity()).sum();

        Double total = sum_action_0 - sum_action_1 - sum_action_2;

        TBSuppliesInWarehouseDTO tBSuppliesInWarehouseDTO = tBSuppliesInWarehouseService.findOne(id).get();
        tBSuppliesInWarehouseDTO.setQuantity(total);
        tBSuppliesInWarehouseService.save(tBSuppliesInWarehouseDTO);
    }

    @Transactional
    public void updateQuantityForTBBlockDetailsDTO(Long id, Long tbBlockDetailsId, Long suppliesId) {
        List<TBStockUpdate> tbStockUpdates = tBStockUpdateRepository.findByTbBlockDetailsId(tbBlockDetailsId);
        Double sum_action_0 = tbStockUpdates.stream()
            .filter(item -> item.getAction() == 0
                && item.getTbSuppliesInWarehouse() != null
                && item.getTbSuppliesInWarehouse().getId() == id)
            .distinct()
            .mapToDouble(o -> o.getQuantity()).sum();

        Double sum_action_1 = tbStockUpdates.stream()
            .filter(item -> item.getAction() == 1
                && item.getTbSuppliesInWarehouse() != null
                && item.getTbSuppliesInWarehouse().getId() == id)
            .distinct()
            .mapToDouble(o -> o.getQuantity()).sum();

        Double sum_action_2 = tbStockUpdates.stream()
            .filter(item -> item.getAction() == 2
                && item.getTbSuppliesInWarehouse() != null
                && item.getTbSuppliesInWarehouse().getId() == id)
            .distinct()
            .mapToDouble(o -> o.getQuantity()).sum();

        Double total = sum_action_0 - sum_action_1 - sum_action_2;

        TBBlockDetailsDTO tbBlockDetailsDTO = tbBlockDetailsService.findOne(tbBlockDetailsId).get();
        tbBlockDetailsDTO.setQuantity(total);
        tbBlockDetailsService.save(tbBlockDetailsDTO);
        // Create TbBlockDetailsLog
        TBSuppliesDTO tBSuppliesDTO = tBSuppliesService.findOne(suppliesId).get();
        TBBlockDetailsReportDTO tbBlockDetailsReportDTO = new TBBlockDetailsReportDTO();
        tbBlockDetailsReportDTO.setTbBlockDetailsId(tbBlockDetailsDTO.getId());
        tbBlockDetailsReportDTO.setTbSuppliesInWarehouseId(id);
        tbBlockDetailsReportDTO.setQuantity(total);
        tbBlockDetailsReportDTO.setCreateDate(Instant.now());
        tbBlockDetailsReportDTO.setCode(tbBlockDetailsDTO.getCode());
        tbBlockDetailsReportDTO.setTbSuppliesTypeId(tBSuppliesDTO.getTbSuppliesTypeId());
        tbBlockDetailsReportService.save(tbBlockDetailsReportDTO);
    }

    public Double totalInventory(Long suppliesId) {
        Double sum = tBSuppliesInWarehouseRepository.findAll().stream()
            .filter(item -> item.getTbSupplies() != null && item.getTbSupplies().getId() == suppliesId)
            .distinct()
            .mapToDouble(o -> o.getQuantity()).sum();
        return sum;
    }

    public List<TBSuppliesInWarehouseDTO> listSuppliesInWarehouse(Long suppliesId) {
        List<TBSuppliesInWarehouse> list = tBSuppliesInWarehouseRepository.findAll().stream()
            .filter(item -> item.getTbSupplies() != null && item.getTbSupplies().getId() == suppliesId)
            .distinct()
            .collect(Collectors.toList());
        List<TBSuppliesInWarehouseDTO> result = tBSuppliesInWarehouseMapper.toDto(list);
        return result;
    }

    public List<TBSuppliesInWarehouseDTO> listSuppliesInWarehouseBySuppliesTypeId(Long suppliesTypeId) {
        List<TBSuppliesInWarehouse> list = tBSuppliesInWarehouseRepository.findAll().stream()
            .filter(item -> item.getTbSupplies() != null
                && item.getTbSupplies().getTbSuppliesType() != null
                && item.getTbSupplies().getTbSuppliesType().getId() == suppliesTypeId
            )
            .distinct()
            .collect(Collectors.toList());
        List<TBSuppliesInWarehouseDTO> result = tBSuppliesInWarehouseMapper.toDto(list);
        return result;
    }

    public TBSuppliesDetailDTO getDetailSupplies(Long suppliesId) {
        TBSuppliesDTO tBSuppliesDTO = tBSuppliesService.findOne(suppliesId).get();
        UserDTO currentUser = userService.getCurrentUserDTO().get();
        if (currentUser.getCustomerId() != null
            && !currentUser.getCustomerId().equals(tBSuppliesDTO.getTbCustomerId())) {
            throw new BadRequestAlertException("1013", ENTITY_NAME, "1013");
        }
        TBSuppliesDetailDTO re = new TBSuppliesDetailDTO();
        re.setTbSuppliesDTO(tBSuppliesDTO);
        re.setTbSuppliesInWarehouseDTOS(listSuppliesInWarehouse(suppliesId));
        return re;
    }

    public Double changeUnit(Long suppliesInWarehouseId, String unit, Double quantity) {
        Double quantity_re;
        TBSuppliesInWarehouseDTO tBSuppliesInWarehouseDTO = tBSuppliesInWarehouseService.findOne(suppliesInWarehouseId).get();
        Long suppliesId = tBSuppliesInWarehouseDTO.getTbSuppliesId();
        TBSuppliesDTO tBSuppliesDTO = tBSuppliesService.findOne(suppliesId).get();
        if (tBSuppliesDTO.getUnit() == unit || unit == null) {
            quantity_re = quantity;
        } else {
            quantity_re = Unit.changeValueUnitWeightDouble(unit, tBSuppliesDTO.getUnit(), quantity);
        }
        return quantity_re;
    }

    public List<TBSuppliesInWarehouseDTO> getListTBSuppliesInWarehouses(String suppliesTypeName, String query) {
        UserDTO currentUser = this.userService.getCurrentUserDTO().get();
        List<TBSuppliesInWarehouse> data;
        if (suppliesTypeName.equals("ALL")) {
            List<TBSuppliesType> tbSuppliesTypeList = tBSuppliesTypeRepository.findAll().stream()
                .filter(item -> item.getName().equals("GIONG"))
                .collect(Collectors.toList());
            TBSuppliesType tbSuppliesTypeDTO = tbSuppliesTypeList.stream().findFirst().get();

            data = tBSuppliesInWarehouseRepository.findAll().stream()
                .filter(item -> item.getTbSupplies()!=null
                    && currentUser.getCustomerId() != null && currentUser.getCustomerId().equals(item.getTbSupplies().getTbCustomer().getId())
                    && item.getTbSupplies().getDeletedBy() ==null && item.getTbSupplies().getDeletedDate()==null)
                .filter(item -> item.getTbSupplies() != null
                    && item.getTbSupplies().getDeletedBy() == null && item.getTbSupplies().getDeletedDate() == null
                    && item.getTbSupplies().getTbSuppliesType() != null
                    && item.getTbSupplies().getTbSuppliesType().getId() != tbSuppliesTypeDTO.getId()
                )
                .distinct()
                .collect(Collectors.toList());
        } else {
            List<TBSuppliesType> tbSuppliesTypeList = tBSuppliesTypeRepository.findAll().stream()
                .filter(item -> item.getName().equals(suppliesTypeName))
                .collect(Collectors.toList());
            TBSuppliesType tbSuppliesTypeDTO = tbSuppliesTypeList.stream().findFirst().get();

            data = tBSuppliesInWarehouseRepository.findAll().stream()
                .filter(item -> item.getTbSupplies() != null
                    && currentUser.getCustomerId() != null && currentUser.getCustomerId().equals(item.getTbSupplies().getTbCustomer().getId())
                    && item.getTbSupplies().getDeletedBy() == null && item.getTbSupplies().getDeletedDate() == null
                    && item.getTbSupplies().getTbSuppliesType() != null
                    && item.getTbSupplies().getTbSuppliesType().getId().equals(tbSuppliesTypeDTO.getId())
                )
                .distinct()
                .collect(Collectors.toList());
        }
        if (query != null && query.equals("") == Boolean.FALSE) {
            data = data.stream().filter(item ->
                item.getTbSupplies() != null
                    && (queryContain(item.getTbSupplies().getName(), query)
                    || queryContain(item.getTbSupplies().getIngredient(), query)
                    || queryContain(item.getTbSupplies().getSku(), query)
                )
            ).collect(Collectors.toList());
        }

        data = data.stream().filter(item -> item.getQuantity() > 0).collect(Collectors.toList());

        List<TBSuppliesInWarehouseDTO> result = tBSuppliesInWarehouseMapper.toDto(data);
        return result;
    }

    public List<TBSuppliesInWarehouseDTO> getListTBSuppliesInWarehousesBySuppliesId(Long suppliesId) {
        List<TBSuppliesInWarehouse> data = tBSuppliesInWarehouseRepository.findAll().stream()
            .filter(item -> item.getTbSupplies() != null && item.getTbSupplies().getId() == suppliesId
                && item.getTbSupplies().getDeletedDate() == null
                && item.getTbSupplies().getDeletedBy() == null
                && item.getDeletedDate() == null
                && item.getDeletedBy() == null
                && item.getTbEntity() != null
                && item.getTbEntity().getId() != null)
            .collect(Collectors.toList());
        List<TBSuppliesInWarehouseDTO> result = tBSuppliesInWarehouseMapper.toDto(data);
        return result;
    }

    private Boolean queryContain(String data, String query) {
        if (data == null) {
            return Boolean.FALSE;
        } else {
            return removeAccent(data.toLowerCase()).contains(removeAccent(query.toLowerCase()));
        }
    }

    public Optional<TBSuppliesDetailDTO> getSeedDetail(Long suppliesId) {
        Optional<TBSuppliesDTO> tbSuppliesDTOOptional = tBSuppliesService.findOne(suppliesId);
        if (!tbSuppliesDTOOptional.isPresent() ||
            tbSuppliesDTOOptional.get().getDeletedById() != null) {
            return Optional.empty();
        }
        TBSuppliesDTO tBSuppliesDTO = tbSuppliesDTOOptional.get();
        if (!GIONG.name().equals(tBSuppliesDTO.getTbSuppliesTypeName())) {
            return Optional.empty();
        }
        TBSuppliesDetailDTO tbSuppliesDetailDTO = new TBSuppliesDetailDTO();
        tbSuppliesDetailDTO.setTbSuppliesDTO(tBSuppliesDTO);
        tbSuppliesDetailDTO.setTbSuppliesInWarehouseDTOS(listSuppliesInWarehouse(suppliesId));
        return Optional.of(tbSuppliesDetailDTO);
    }
}
