package vn.azteam.tpf.service.impl;

import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import vn.azteam.tpf.domain.TBCustomerHasCropType;
import vn.azteam.tpf.domain.TBDetailUser;
import vn.azteam.tpf.repository.TBDetailUserRepository;
import vn.azteam.tpf.service.TBCropService;
import vn.azteam.tpf.domain.TBCrop;
import vn.azteam.tpf.repository.TBCropRepository;
import vn.azteam.tpf.repository.search.TBCropSearchRepository;
import vn.azteam.tpf.service.dto.CropPersonInChargeDTO;
import vn.azteam.tpf.service.dto.TBCropDTO;
import vn.azteam.tpf.service.dto.TBCustomerHasCropTypeDTO;
import vn.azteam.tpf.service.mapper.TBCropMapper;
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 vn.azteam.tpf.service.mapper.TBCustomerHasCropTypeMapper;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

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

/**
 * Service Implementation for managing TBCrop.
 */
@Service
@Transactional
public class TBCropServiceImpl implements TBCropService {

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

    private final TBCropRepository tBCropRepository;

    private final TBCropMapper tBCropMapper;

    private final TBCropSearchRepository tBCropSearchRepository;

    private final TBDetailUserRepository detailUserRepository;

    private final TBCustomerHasCropTypeMapper tbCustomerHasCropTypeMapper;

    private final EntityManager em;

    private final ElasticsearchOperations elasticsearchTemplate;

    public TBCropServiceImpl(TBCropRepository tBCropRepository, TBCropMapper tBCropMapper, TBCropSearchRepository tBCropSearchRepository, TBDetailUserRepository detailUserRepository, TBCustomerHasCropTypeMapper tbCustomerHasCropTypeMapper, EntityManager em, ElasticsearchOperations elasticsearchTemplate) {
        this.tBCropRepository = tBCropRepository;
        this.tBCropMapper = tBCropMapper;
        this.tBCropSearchRepository = tBCropSearchRepository;
        this.detailUserRepository = detailUserRepository;
        this.tbCustomerHasCropTypeMapper = tbCustomerHasCropTypeMapper;
        this.em = em;
        this.elasticsearchTemplate = elasticsearchTemplate;
    }

    /**
     * Save a tBCrop.
     *
     * @param tBCropDTO the entity to save
     * @return the persisted entity
     */
    @Override
    public TBCropDTO save(TBCropDTO tBCropDTO) {
        log.debug("Request to save TBCrop : {}", tBCropDTO);

        TBCrop tBCrop = tBCropMapper.toEntity(tBCropDTO);
        tBCrop = tBCropRepository.save(tBCrop);
        TBCropDTO result = tBCropMapper.toDto(tBCrop);
        tBCropSearchRepository.save(tBCrop);
        return result;
    }

    @Override
    public Optional<TBCropDTO> updateCropPersonInCharge(CropPersonInChargeDTO cropPersonInChargeDTO) {
        return Optional.of(tBCropRepository
            .findById(cropPersonInChargeDTO.getCropId()))
            .filter(Optional::isPresent)
            .map(Optional::get)
            .map(crop -> {
                Set<TBDetailUser> detailUsers = crop.getTbDetailUsers();
                detailUsers.clear();
                cropPersonInChargeDTO.getDetailUserIds().stream()
                    .map(detailUserRepository::findById)
                    .filter(Optional::isPresent)
                    .map(Optional::get)
                    .forEach(detailUsers::add);
                return crop;
            })
            .map(crop -> {
                tBCropSearchRepository.save(crop);
                return crop;
            })
            .map(tBCropMapper::toDto);
    }

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

    /**
     * Get all the TBCrop with eager load of many-to-many relationships.
     *
     * @return the list of entities
     */
    public Page<TBCropDTO> findAllWithEagerRelationships(Pageable pageable) {
        return tBCropRepository.findAllWithEagerRelationships(pageable).map(tBCropMapper::toDto);
    }


    /**
     * Get one tBCrop by id.
     *
     * @param id the id of the entity
     * @return the entity
     */
    @Override
    @Transactional(readOnly = true)
    public Optional<TBCropDTO> findOne(Long id) {
        log.debug("Request to get TBCrop : {}", id);
        return tBCropRepository.findOneWithEagerRelationships(id)
            .map(tBCropMapper::toDto);
    }

    /**
     * Delete the tBCrop by id.
     *
     * @param id the id of the entity
     */
    @Override
    public void delete(Long id) {
        log.debug("Request to delete TBCrop : {}", id);
        tBCropRepository.deleteById(id);
        tBCropSearchRepository.deleteById(id);
    }

    /**
     * Search for the tBCrop 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<TBCropDTO> search(String query, Pageable pageable) {
        log.debug("Request to search for a page of TBCrops for query {}", query);
        return tBCropSearchRepository.search(queryStringQuery(query), pageable)
            .map(tBCropMapper::toDto);
    }

    /**
     * Search for the tBCrop corresponding to the query, areaId or netHouseId.
     *
     * @param query      the query of the search
     * @param areaId     the areaId of the search
     * @param netHouseId the netHouseId of the search
     * @param pageable   the pagination information
     * @return
     */
    @Override
    @Transactional(readOnly = true)
    public Page<TBCropDTO> searchCrop(String query, Long areaId, Long netHouseId, Pageable pageable) {
//        if (areaId == null && netHouseId == null) {
//            return this.search(query, pageable);
//        }



        NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder();
        if (query != null) {
            searchQueryBuilder.withFilter(boolQuery().should(termQuery("code", query)));
            searchQueryBuilder.withFilter(boolQuery().should(termQuery("tbSupplies.sku", query)));
            searchQueryBuilder.withFilter(boolQuery().should(termQuery("status", query)));
            searchQueryBuilder.withFilter(boolQuery().should(termQuery("tbDetailUsers.fullName", query)));
        }

        if (areaId != null) {
            searchQueryBuilder.withFilter(boolQuery().must(termQuery("tbEntity.id", areaId)));
        }
        if (netHouseId != null) {
            searchQueryBuilder.withFilter(boolQuery().must(termQuery("tbEntity.tbEntity.id", netHouseId)));
        }

        NativeSearchQuery searchQuery = searchQueryBuilder
            .withPageable(pageable)
            .build();

        Page<TBCropDTO> page = elasticsearchTemplate.queryForPage(searchQuery, TBCrop.class).map(tBCropMapper::toDto);
        return page;
    }

    @Override
    @Transactional(readOnly = true)
    public List<TBCustomerHasCropTypeDTO> getAllCustomerHasCropTypeByCustomerAndCropType(Long customerId, Long cropTypeId){
        String query = "SELECT * \n" +
            "FROM tb_customer_has_crop_type AS cct \n" +
            "WHERE cct.tb_customer_id = :customerId ";
        if(cropTypeId != null){
            query += "AND cct.tb_crop_type_id = :cropTypeId";
        }
        Query nativeQuery = em.createNativeQuery(query , TBCustomerHasCropType.class);
        nativeQuery.setParameter("customerId", customerId);
        if(cropTypeId != null){
            nativeQuery.setParameter("cropTypeId", cropTypeId);
        }
        return tbCustomerHasCropTypeMapper.toDto(nativeQuery.getResultList());
    }

    @Override
    @Transactional(readOnly = true)
    public List<Long> getAllCropIdByCustomer(Long customerId){
        String query = "SELECT DISTINCT (crp.id)  \n" +
            "FROM smf.tb_crop AS crp \n" +
            "LEFT JOIN smf.tb_entity AS ent ON crp.tb_entity_id = ent.id \n" +
            "WHERE ent.tb_customer_id = :customerId ";

        Query cropOnCustomerQuery = em.createNativeQuery(query);
        cropOnCustomerQuery.setParameter("customerId", customerId);
        List<BigInteger> cropIds = cropOnCustomerQuery.getResultList();

        List<Long> finalResult = new ArrayList<>();
        for(BigInteger cropId : cropIds ) {
            finalResult.add(cropId.longValue());
        }
        return finalResult;
    }

    public List<Long> getListActivityTypeIdByByCustomerAndCropType(Long customerId, Long cropTypeId){
        List<TBCustomerHasCropTypeDTO> tbCustomerHasCropTypeDTOS = getAllCustomerHasCropTypeByCustomerAndCropType(customerId,cropTypeId);
        List<Long> result = tbCustomerHasCropTypeDTOS.stream()
            .map(TBCustomerHasCropTypeDTO::getTbActivityTypeId)
            .collect(Collectors.toList());
        return result;
    }

    public List<String> getListActivityTypeNameByByCustomerAndCropType(Long customerId, Long cropTypeId){
        List<TBCustomerHasCropTypeDTO> tbCustomerHasCropTypeDTOS = getAllCustomerHasCropTypeByCustomerAndCropType(customerId,cropTypeId);
        List<String> result = tbCustomerHasCropTypeDTOS.stream()
            .map(TBCustomerHasCropTypeDTO::getTbActivityTypeName)
            .collect(Collectors.toList());
        return result;
    }
}
