package vn.azteam.tpf.service;

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

import javax.persistence.criteria.JoinType;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
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.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import io.github.jhipster.service.QueryService;

import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import vn.azteam.tpf.domain.TBEquipment;
import vn.azteam.tpf.domain.*; // for static metamodels
import vn.azteam.tpf.repository.TBEquipmentRepository;
import vn.azteam.tpf.repository.search.TBEquipmentSearchRepository;
import vn.azteam.tpf.service.dto.*;
import vn.azteam.tpf.service.mapper.TBEquipmentMapper;

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

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

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

    private final TBEquipmentRepository tBEquipmentRepository;

    private final TBEquipmentMapper tBEquipmentMapper;

    private final UserService userService;

    private final TBEquipmentService tBEquipmentService;

    private final TBEquipmentSearchRepository tBEquipmentSearchRepository;

    private final TBEquipmentOfCustomerQueryService tBEquipmentOfCustomerQueryService;

    private final FileStorageService fileStorageService;

    private final ObjectMapper objectMapper;


    public TBEquipmentQueryService(TBEquipmentRepository tBEquipmentRepository,
                                   TBEquipmentMapper tBEquipmentMapper,
                                   UserService userService,
                                   TBEquipmentService tBEquipmentService, TBEquipmentSearchRepository tBEquipmentSearchRepository,
                                   TBEquipmentOfCustomerQueryService tBEquipmentOfCustomerQueryService,
                                   FileStorageService fileStorageService,
                                   ObjectMapper objectMapper) {
        this.tBEquipmentRepository = tBEquipmentRepository;
        this.tBEquipmentMapper = tBEquipmentMapper;
        this.userService = userService;
        this.tBEquipmentService = tBEquipmentService;
        this.tBEquipmentSearchRepository = tBEquipmentSearchRepository;
        this.tBEquipmentOfCustomerQueryService = tBEquipmentOfCustomerQueryService;
        this.fileStorageService = fileStorageService;
        this.objectMapper = objectMapper;
    }

    /**
     * Return a {@link List} of {@link TBEquipmentDTO} 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<TBEquipmentDTO> findByCriteria(TBEquipmentCriteria criteria) {
        log.debug("find by criteria : {}", criteria);
        final Specification<TBEquipment> specification = createSpecification(criteria);
        return tBEquipmentMapper.toDto(tBEquipmentRepository.findAll(specification));
    }

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

    /**
     * Function to convert TBEquipmentCriteria to a {@link Specification}
     */
    private Specification<TBEquipment> createSpecification(TBEquipmentCriteria criteria) {
        Specification<TBEquipment> specification = Specification.where(null);
        if (criteria != null) {
            if (criteria.getId() != null) {
                specification = specification.and(buildSpecification(criteria.getId(), TBEquipment_.id));
            }
            if (criteria.getSku() != null) {
                specification = specification.and(buildStringSpecification(criteria.getSku(), TBEquipment_.sku));
            }
            if (criteria.getName() != null) {
                specification = specification.and(buildStringSpecification(criteria.getName(), TBEquipment_.name));
            }
            if (criteria.getManufacturer() != null) {
                specification = specification.and(buildStringSpecification(criteria.getManufacturer(), TBEquipment_.manufacturer));
            }
            if (criteria.getSpecs() != null) {
                specification = specification.and(buildStringSpecification(criteria.getSpecs(), TBEquipment_.specs));
            }
            if (criteria.getCreatedDate() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getCreatedDate(), TBEquipment_.createdDate));
            }
            if (criteria.getModifiedDate() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getModifiedDate(), TBEquipment_.modifiedDate));
            }
            if (criteria.getDeletedDate() != null) {
                specification = specification.and(buildRangeSpecification(criteria.getDeletedDate(), TBEquipment_.deletedDate));
            }
            if (criteria.getTbEquipmentTypeId() != null) {
                specification = specification.and(buildSpecification(criteria.getTbEquipmentTypeId(),
                    root -> root.join(TBEquipment_.tbEquipmentType, JoinType.LEFT).get(TBEquipmentType_.id)));
            }
            if (criteria.getCreatedById() != null) {
                specification = specification.and(buildSpecification(criteria.getCreatedById(),
                    root -> root.join(TBEquipment_.createdBy, JoinType.LEFT).get(TBDetailUser_.id)));
            }
            if (criteria.getModifiedById() != null) {
                specification = specification.and(buildSpecification(criteria.getModifiedById(),
                    root -> root.join(TBEquipment_.modifiedBy, JoinType.LEFT).get(TBDetailUser_.id)));
            }
            if (criteria.getDeletedById() != null) {
                specification = specification.and(buildSpecification(criteria.getDeletedById(),
                    root -> root.join(TBEquipment_.deletedBy, JoinType.LEFT).get(TBDetailUser_.id)));
            }
            if (criteria.getTbCustomerId() != null) {
                specification = specification.and(buildSpecification(criteria.getTbCustomerId(),
                    root -> root.join(TBEquipment_.tbCustomer, JoinType.LEFT).get(TBCustomer_.id)));
            }
        }
        return specification;
    }

    public TBEquipmentDTO createTBEquipments(TBEquipmentDTO data, MultipartFile[] files) {
        UserDTO currentUser = userService.getCurrentUserDTO().get();
        data = saveUploadedFiles(data, files);
        data.setCreatedById(currentUser.getId());
        data.setCreatedDate(Instant.now());
        return data;
    }

    @Transactional
    public Boolean deleteTBEquipment(Long id) {
        try {
            UserDTO currentUser = userService.getCurrentUserDTO().get();
            TBEquipmentDTO dataEquipments = tBEquipmentService.findOne(id).get();
            dataEquipments.setDeletedById(currentUser.getId());
            dataEquipments.setDeletedDate(Instant.now());
            tBEquipmentService.save(dataEquipments);
            return Boolean.TRUE;
        } catch (Exception e) {
            return Boolean.FALSE;
        }
    }

    @Transactional
    public TBEquipmentDTO updateTBEquipments(TBEquipmentDTO data, MultipartFile[] files) {
        UserDTO currentUser = userService.getCurrentUserDTO().get();
        TBEquipmentDTO dataEquipments = tBEquipmentService.findOne(data.getId()).get();
        data.setSpecs(dataEquipments.getSpecs());
        data = removeFiles(data);
        data = updateUploadedFiles(data, files);
        dataEquipments.setModifiedById(currentUser.getId());
        dataEquipments.setModifiedDate(Instant.now());
        dataEquipments.setSpecs(data.getSpecs());
        dataEquipments.setName(data.getName());
        dataEquipments.setSku(data.getSku());
        dataEquipments.setManufacturer(data.getManufacturer());
        dataEquipments.setTbEquipmentTypeId(data.getTbEquipmentTypeId());
        TBEquipmentDTO result = tBEquipmentService.save(dataEquipments);
        return result;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public TBEquipmentDTO removeFiles(TBEquipmentDTO data) {
        if(data.getGuideDel() != null) {
            List<SpecsTBEquipmentDTO> files = parseSpecsTBEquipmentDTO(data.getSpecs());
            List<SpecsTBEquipmentDTO> deleteFiles = data.getGuideDel();
            if (null != deleteFiles) {
                for (SpecsTBEquipmentDTO item : deleteFiles) {
                    this.fileStorageService.removeFile("", item.getMedia());
                    files.removeIf(item_ -> item_.getTitle().equals(item.getTitle())
                        && item_.getMedia().equals(item.getMedia()));
                }
                data.setSpecs(parseJsonToString(files));
            }
        }
        return data;
    }
    public String parseJsonToString(List<SpecsTBEquipmentDTO> data){
        String path = null;
        int i = 0;
        for (SpecsTBEquipmentDTO item : data){
            if (i == 0) {
                path = "{\"title\":\"" + item.getTitle() + "\",\"media\":\"" + item.getMedia() + "\"}";
            } else {
                path = path + ";{\"title\":\"" + item.getTitle() + "\",\"media\":\"" + item.getMedia() + "\"}";
            }
            i = i+1;
        }
        return path;
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public TBEquipmentDTO saveUploadedFiles(TBEquipmentDTO data, MultipartFile[] files) {
        String filesPath;
        if (null != files && files.length > 0) {
            filesPath = this.fileStorageService.storeFiles("docs", files);
            ArrayList<String> lstFile = new ArrayList<>();
            if (!StringUtils.isEmpty(filesPath)) {
                lstFile.addAll(Arrays.asList(filesPath.split(";")));
            }
            String path = null;
            int i;
            for (i = 0; i < lstFile.size(); i++) {
                if (i == 0) {
                    path = "{\"title\":\"" + files[i].getOriginalFilename() + "\",\"media\":\"" + lstFile.get(i) + "\"}";
                } else {
                    path = path + ";{\"title\":\"" + files[i].getOriginalFilename() + "\",\"media\":\"" + lstFile.get(i) + "\"}";
                }
            }
            data.setSpecs(path);
        } else {
            data.setSpecs(null);
        }
        return data;
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public TBEquipmentDTO updateUploadedFiles(TBEquipmentDTO data, MultipartFile[] files) {
        String filesPath;
        if (null != files && files.length > 0) {
            filesPath = this.fileStorageService.storeFiles("docs", files);
            ArrayList<String> lstFile = new ArrayList<>();
            if (!StringUtils.isEmpty(filesPath)) {
                lstFile.addAll(Arrays.asList(filesPath.split(";")));
            }
            String path = data.getSpecs();
            int i;
            for (i = 0; i < lstFile.size(); i++)  {
                if (path == null) {
                    path = "{\"title\":\"" + files[i].getOriginalFilename() + "\",\"media\":\"" + lstFile.get(i) + "\"}";
                } else {
                    path = path + ";{\"title\":\"" + files[i].getOriginalFilename() + "\",\"media\":\"" + lstFile.get(i) + "\"}";
                }
            }
            data.setSpecs(path);
        }
        return data;
    }

    public Page<TBEquipmentDTO> getAllTBEquipments(TBEquipmentCriteria criteria, Pageable page, String query, Long tbEquipmentTypeId) {
        UserDTO currentUser = userService.getCurrentUserDTO().get();
        List<TBEquipment> data = tBEquipmentRepository.findAll().stream()
            .filter(item -> item.getDeletedBy() == null && item.getDeletedDate() == null)
            .collect(Collectors.toList());

        if (currentUser.getCustomerId() != null){
            data = data.stream().filter(item -> item.getTbCustomer() == null
                || (item.getTbCustomer() != null && item.getTbCustomer().getId() == currentUser.getCustomerId()))
                .collect(Collectors.toList());
        }

        if (tbEquipmentTypeId != null) {
            data = data.stream().filter(item ->
                item.getTbEquipmentType() != null && item.getTbEquipmentType().getId() == tbEquipmentTypeId)
                .collect(Collectors.toList());
        }

        if (query != null && query.equals("") == Boolean.FALSE) {
            data = data.stream()
                .filter(item ->
                    queryContain(item.getName(), query)
                        || queryContain(item.getManufacturer(), query)
                        || queryContain(item.getSpecs(), query)
                        || (item.getTbEquipmentType() != null && queryContain(item.getTbEquipmentType().getName(), query))
                )
                .collect(Collectors.toList());
        }

        List<Long> ids = data.stream().map(TBEquipment::getId).collect(Collectors.toList());
        if (ids.size() > 0) {
            LongFilter idFilter = new LongFilter();
            idFilter.setIn(ids);
            criteria.setId(idFilter);


            final Specification<TBEquipment> specification = createSpecification(criteria);
            Page<TBEquipmentDTO> pages = tBEquipmentRepository.findAll(specification, page)
                .map(tBEquipmentMapper::toDto);
            pages.forEach(item -> {
                item.setGuide(parseSpecsTBEquipmentDTO(item.getSpecs()));
                item.setSpecs(null);
                item.setInventory(tBEquipmentOfCustomerQueryService.totalDeviceInWarehouse(item.getId()));
            });
            return pages;
        } else {
            return new PageImpl<>(Lists.newArrayList(), page, 0);
        }
    }

    public List<SpecsTBEquipmentDTO> parseSpecsTBEquipmentDTO(String data_request) {
        List<SpecsTBEquipmentDTO> data = new ArrayList<>();
        ArrayList<String> listSpecs = new ArrayList<>();
        if (!StringUtils.isEmpty(data_request)) {
            listSpecs.addAll(Arrays.asList(data_request.split(";")));
        }
        int i;
        for (i = 0; i < listSpecs.size(); i++) {
            try {
                data.add(this.objectMapper.readValue(listSpecs.get(i), SpecsTBEquipmentDTO.class));
            } catch (Exception e) {
                return null;
            }
        }
        return data;
    }

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

    public List<TBEntityDTO> getAllTBEquipmentsInWarehouse(Long tbEquipmentId) {
        return tBEquipmentOfCustomerQueryService.listWarehouse(tbEquipmentId);
    }
}
