package vn.azteam.tpf.service;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javax.persistence.Column;
import javax.persistence.EntityManager;
import javax.persistence.Parameter;
import javax.persistence.Query;
import javax.persistence.SqlResultSetMapping;
import javax.persistence.Table;
import javax.persistence.Tuple;
import javax.persistence.criteria.JoinType;

import org.apache.commons.compress.utils.Lists;
import org.hibernate.transform.Transformers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
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.TBProductBlockDetails;
import vn.azteam.tpf.Constants.Constants;
import vn.azteam.tpf.domain.*; // for static metamodels
import vn.azteam.tpf.repository.TBProductBlockDetailsRepository;
import vn.azteam.tpf.repository.search.TBProductBlockDetailsSearchRepository;
import vn.azteam.tpf.service.dto.TBProductBlockDetailsCriteria;
import vn.azteam.tpf.service.dto.TBProductBlockDetailsDTO;
import vn.azteam.tpf.service.mapper.TBProductBlockDetailsMapper;
import vn.azteam.tpf.service.util.NativeQueryUtil;

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

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

    private final TBProductBlockDetailsRepository tBProductBlockDetailsRepository;

    private final TBProductBlockDetailsMapper tBProductBlockDetailsMapper;

    private final TBProductBlockDetailsSearchRepository tBProductBlockDetailsSearchRepository;

    private final EntityManager em;

    public TBProductBlockDetailsQueryService(TBProductBlockDetailsRepository tBProductBlockDetailsRepository,
                                             TBProductBlockDetailsMapper tBProductBlockDetailsMapper,
                                             TBProductBlockDetailsSearchRepository tBProductBlockDetailsSearchRepository,
                                             EntityManager em) {
        this.tBProductBlockDetailsRepository = tBProductBlockDetailsRepository;
        this.tBProductBlockDetailsMapper = tBProductBlockDetailsMapper;
        this.tBProductBlockDetailsSearchRepository = tBProductBlockDetailsSearchRepository;
        this.em = em;
    }

    /**
     * Return a {@link List} of {@link TBProductBlockDetailsDTO} 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<TBProductBlockDetailsDTO> findByCriteria(TBProductBlockDetailsCriteria criteria) {
        log.debug("find by criteria : {}", criteria);
        final Specification<TBProductBlockDetails> specification = createSpecification(criteria);
        return tBProductBlockDetailsMapper.toDto(tBProductBlockDetailsRepository.findAll(specification));
    }

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

    /**
     * Function to convert TBProductBlockDetailsCriteria to a {@link Specification}
     */
    private Specification<TBProductBlockDetails> createSpecification(TBProductBlockDetailsCriteria criteria) {
        Specification<TBProductBlockDetails> specification = Specification.where(null);

        if (criteria != null) {
            if (criteria.getId() != null) {
                specification = specification.and(buildSpecification(criteria.getId(), TBProductBlockDetails_.id));
            }
            if (criteria.getQuantity() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getQuantity(), TBProductBlockDetails_.quantity));
            }
            if (criteria.getCode() != null) {
                specification = specification.and(buildStringSpecification(criteria.getCode(), TBProductBlockDetails_.code));
            }
            if (criteria.getHarvestDate() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getHarvestDate(), TBProductBlockDetails_.harvestDate));
            }
            if (criteria.getExpDate() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getExpDate(), TBProductBlockDetails_.expDate));
            }
            if (criteria.getProductLevel() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getProductLevel(), TBProductBlockDetails_.productLevel));
            }
            if (criteria.getTbProductUnitId() != null) {
                specification = specification.and(buildSpecification(criteria.getTbProductUnitId(),
                    root -> root.join(TBProductBlockDetails_.tbProductUnit, JoinType.LEFT).get(TBProductUnit_.id)));
            }
            if (criteria.getTbCropId() != null) {
                specification = specification.and(buildSpecification(criteria.getTbCropId(),
                    root -> root.join(TBProductBlockDetails_.tbCrop, JoinType.LEFT).get(TBCrop_.id)));
            }
            if (criteria.getTbEntityId() != null) {
                specification = specification.and(buildSpecification(criteria.getTbEntityId(),
                    root -> root.join(TBProductBlockDetails_.tbEntity, JoinType.LEFT).get(TBEntity_.id)));
            }
        }
        return specification;
    }


    public Page<TBProductBlockDetailsDTO> nativeSearch(TBProductBlockDetailsCriteria criteria, Pageable pageable) {
        String subSqlPsu = "SELECT \n" +
            " subPsu.tb_product_block_details_id as blockId, \n" +
            " SUM(subPsu.quantity) as importQuantity, \n" +
            " GROUP_CONCAT(DISTINCT subPsu.description ORDER BY subPsu.description ASC SEPARATOR ';') as blockDescription" +
            " FROM "+TBProductStockUpdate.class.getAnnotation(Table.class).name()+" subPsu \n" +
            " WHERE subPsu.action = :action \n" +
            " GROUP BY subPsu.tb_product_block_details_id \n";

        String sqlSelectString = "SELECT \n" +
            "pbd.id, " +
            "pbd.quantity, " +
            "pbd.code, " +
            "pbd.harvest_date as harvestDate, " +
            "pbd.exp_date as expDate, " +
            "pbd.product_level as productLevel, " +
            "pbd.tb_product_unit_id as tbProductUnitId, " +
            "pu.name as tbProductUnitName, " +
            "pbd.tb_crop_id as tbCropId, " +
            "c.code as tbCropCode, " +
            "pbd.tb_entity_id as tbEntityId, " +
            "w.name as tbEntityName, " +
            "s.name as tbSuppliesName, " +
            "psu.importQuantity, \n" +
            "psu.blockDescription as description \n";

        String sqlString =
            "FROM "+TBProductBlockDetails.class.getAnnotation(Table.class).name()+" pbd \n" +
            "LEFT JOIN ("+subSqlPsu+") psu \n" +
            "ON pbd.id = psu.blockId \n" +
            "LEFT JOIN "+TBProductUnit.class.getAnnotation(Table.class).name()+" pu \n" +
            "ON pbd.tb_product_unit_id = pu.id \n" +
            "LEFT JOIN "+TBCrop.class.getAnnotation(Table.class).name()+" c \n" +
            "ON pbd.tb_crop_id = c.id \n" +
            "LEFT JOIN "+TBSupplies.class.getAnnotation(Table.class).name()+" s \n" +
            "ON c.tb_supplies_id = s.id \n" +
            "LEFT JOIN "+TBEntity.class.getAnnotation(Table.class).name()+" w \n" +
            "ON pbd.tb_entity_id = w.id \n" +
            "WHERE 1 = 1 \n";

        Map<String, Object> params = new HashMap<>();

        if (criteria.getTbCropCode() != null && criteria.getTbCropCode().getContains() != "") {
            sqlString += "AND c.code LIKE CONCAT('%',:cropCode,'%') \n";
            params.put("cropCode", criteria.getTbCropCode().getContains());
        }

        if (criteria.getTbSuppliesId() != null && criteria.getTbSuppliesId().getEquals() != null) {
            sqlString += "AND c.tb_supplies_id = :suppliesId \n";
            params.put("suppliesId", criteria.getTbSuppliesId().getEquals());
        }

        if (criteria.getProductLevel() != null && criteria.getProductLevel().getEquals() != null) {
            sqlString += "AND pbd.product_level = :productLevel \n";
            params.put("productLevel", criteria.getProductLevel().getEquals());
        }

        if (pageable.getSort() != null && pageable.getSort().isSorted()) {
            sqlString += NativeQueryUtil.getSortQuery(pageable, TBProductBlockDetails.class);
        }

        Query query = NativeQueryUtil.create(em, sqlSelectString + sqlString, params);

        query.setParameter("action", Constants.PRODUCT_STOCK_UPDATE_ACTION.IMPORT.getValue());

        int total = NativeQueryUtil.getTotalCount(em, query, sqlString);
        List<TBProductBlockDetailsDTO> dtos = NativeQueryUtil.getPageResultList(pageable, query)
            .stream()
            .map(TBProductBlockDetailsDTO::new)
            .collect(Collectors.toList());

        return new PageImpl<TBProductBlockDetailsDTO>(dtos, pageable, total);
    }
}
