package vn.azteam.tpf.service;

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

import javax.persistence.criteria.JoinType;

import io.github.jhipster.service.filter.LongFilter;
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.TBStockUpdate;
import vn.azteam.tpf.domain.*; // for static metamodels
import vn.azteam.tpf.repository.TBStockUpdateRepository;
import vn.azteam.tpf.repository.search.TBStockUpdateSearchRepository;
import vn.azteam.tpf.service.dto.*;
import vn.azteam.tpf.service.mapper.TBStockUpdateMapper;
import vn.azteam.tpf.web.rest.errors.BadRequestAlertException;

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

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

    private final TBStockUpdateRepository tBStockUpdateRepository;

    private final TBStockUpdateMapper tBStockUpdateMapper;

    private final TBStockUpdateSearchRepository tBStockUpdateSearchRepository;

    private final TBSuppliesInWarehouseQueryService tBSuppliesInWarehouseQueryService;

    private final TBStockUpdateService tBStockUpdateService;

    private final TBSuppliesInWarehouseService tBSuppliesInWarehouseService;

    public TBStockUpdateQueryService(TBStockUpdateRepository tBStockUpdateRepository,
                                     TBStockUpdateMapper tBStockUpdateMapper,
                                     TBStockUpdateSearchRepository tBStockUpdateSearchRepository,
                                     TBSuppliesInWarehouseQueryService tBSuppliesInWarehouseQueryService,
                                     TBStockUpdateService tBStockUpdateService, TBSuppliesInWarehouseService tBSuppliesInWarehouseService) {
        this.tBStockUpdateRepository = tBStockUpdateRepository;
        this.tBStockUpdateMapper = tBStockUpdateMapper;
        this.tBStockUpdateSearchRepository = tBStockUpdateSearchRepository;
        this.tBSuppliesInWarehouseQueryService = tBSuppliesInWarehouseQueryService;
        this.tBStockUpdateService = tBStockUpdateService;
        this.tBSuppliesInWarehouseService = tBSuppliesInWarehouseService;
    }

    /**
     * Return a {@link List} of {@link TBStockUpdateDTO} 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<TBStockUpdateDTO> findByCriteria(TBStockUpdateCriteria criteria) {
        log.debug("find by criteria : {}", criteria);
        final Specification<TBStockUpdate> specification = createSpecification(criteria);
        return tBStockUpdateMapper.toDto(tBStockUpdateRepository.findAll(specification));
    }

    /**
     * Return a {@link Page} of {@link TBStockUpdateDTO} 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<TBStockUpdateDTO> findByCriteria(TBStockUpdateCriteria criteria, Pageable page) {
        log.debug("find by criteria : {}, page: {}", criteria, page);
        final Specification<TBStockUpdate> specification = createSpecification(criteria);
        return tBStockUpdateRepository.findAll(specification, page)
            .map(tBStockUpdateMapper::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(TBStockUpdateCriteria criteria) {
        log.debug("count by criteria : {}", criteria);
        final Specification<TBStockUpdate> specification = createSpecification(criteria);
        return tBStockUpdateRepository.count(specification);
    }

    /**
     * Function to convert TBStockUpdateCriteria to a {@link Specification}
     */
    private Specification<TBStockUpdate> createSpecification(TBStockUpdateCriteria criteria) {
        Specification<TBStockUpdate> specification = Specification.where(null);
        if (criteria != null) {
            if (criteria.getId() != null) {
                specification = specification.and(buildSpecification(criteria.getId(), TBStockUpdate_.id));
            }
            if (criteria.getUpdateDate() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getUpdateDate(), TBStockUpdate_.updateDate));
            }
            if (criteria.getAction() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getAction(), TBStockUpdate_.action));
            }
            if (criteria.getQuantity() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getQuantity(), TBStockUpdate_.quantity));
            }
            if (criteria.getDescription() != null) {
                specification = specification.and(buildStringSpecification(criteria.getDescription(), TBStockUpdate_.description));
            }
            if (criteria.getTbSuppliesOrderId() != null) {
                specification = specification.and(buildSpecification(criteria.getTbSuppliesOrderId(),
                    root -> root.join(TBStockUpdate_.tbSuppliesOrder, JoinType.LEFT).get(TBSuppliesOrder_.id)));
            }
            if (criteria.getTbSuppliesInWarehouseId() != null) {
                specification = specification.and(buildSpecification(criteria.getTbSuppliesInWarehouseId(),
                    root -> root.join(TBStockUpdate_.tbSuppliesInWarehouse, JoinType.LEFT).get(TBSuppliesInWarehouse_.id)));
            }
            if (criteria.getTbActivityId() != null) {
                specification = specification.and(buildSpecification(criteria.getTbActivityId(),
                    root -> root.join(TBStockUpdate_.tbActivity, JoinType.LEFT).get(TBActivity_.id)));
            }
            if (criteria.getTbBlockDetailsId() != null) {
                specification = specification.and(buildSpecification(criteria.getTbBlockDetailsId(),
                    root -> root.join(TBStockUpdate_.tbBlockDetails, JoinType.LEFT).get(TBBlockDetails_.id)));
            }
        }
        return specification;
    }

    public Page<TBStockUpdateDTO> getHistoryTBStockUpdate(TBStockUpdateCriteria criteria, Pageable pageable, Long suppliesId) {
        List<TBSuppliesInWarehouseDTO> tbSuppliesInWarehouseIds = tBSuppliesInWarehouseQueryService.listSuppliesInWarehouse(suppliesId);
        List<Long> listIds = tbSuppliesInWarehouseIds
            .stream()
            .map(TBSuppliesInWarehouseDTO::getId)
            .collect(Collectors.toList());
        if (listIds.isEmpty()) {
            return Page.empty();
        }
        LongFilter suppliesInWarehouseIdFilter = new LongFilter();
        suppliesInWarehouseIdFilter.setIn(listIds);
        criteria.setTbSuppliesInWarehouseId(suppliesInWarehouseIdFilter);

        final Specification<TBStockUpdate> specification = createSpecification(criteria);
        return tBStockUpdateRepository.findAll(specification, pageable)
            .map(tBStockUpdateMapper::toDto);
    }

    public TBStockUpdateDTO createStockUpdate(
        Long activityId,
        Long suppliesInWarehouseId,
        Instant executeDate,
        Double quantity,
        String unit,
        String description
    ) {
        quantity = tBSuppliesInWarehouseQueryService.changeUnit(suppliesInWarehouseId, unit, quantity);
        TBSuppliesInWarehouseDTO tBSuppliesInWarehouseDTO = tBSuppliesInWarehouseService.findOne(suppliesInWarehouseId).get();
        if (quantity > tBSuppliesInWarehouseDTO.getQuantity()) {
            throw new BadRequestAlertException("1021", "not_enough_stock", "1021");
//            throw new BadRequestAlertException("quantity is not enough update stock", "tBSuppliesInWarehouse", "not null");
        }
        // nhập phần xuất kho
        TBStockUpdateDTO tbStockUpdateDTO = new TBStockUpdateDTO();
        tbStockUpdateDTO.setDescription(description);
        tbStockUpdateDTO.setAction(1);
        tbStockUpdateDTO.setQuantity(quantity);
        tbStockUpdateDTO.setUpdateDate(executeDate == null ? Instant.now() : executeDate);
        tbStockUpdateDTO.setTbSuppliesInWarehouseId(suppliesInWarehouseId);
        tbStockUpdateDTO.setTbActivityId(activityId);
        TBStockUpdateDTO result = tBStockUpdateService.save(tbStockUpdateDTO);
        tBSuppliesInWarehouseQueryService.updateQuantityForTBSuppliesInWarehouseDTO(suppliesInWarehouseId);
        return result;
    }

    public void removeStockUpdatebyActivityId(Long activityId) {
        List<TBStockUpdate> tbStockUpdate = tBStockUpdateRepository.findAll().stream()
            .filter(item -> item.getTbActivity() != null && item.getTbActivity().getId() == activityId)
            .collect(Collectors.toList());
        tbStockUpdate.forEach(item -> {
            Long tBStockUpdateId = item.getId();
            Long itemSuppliesInWarehouseId = item.getTbSuppliesInWarehouse().getId();
            tBStockUpdateService.delete(tBStockUpdateId);
            tBSuppliesInWarehouseQueryService.updateQuantityForTBSuppliesInWarehouseDTO(itemSuppliesInWarehouseId);
        });
    }
}
