package org.egl_cepgl.pm.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.egl_cepgl.pm.dto.*;
import org.egl_cepgl.pm.model.*;
import org.egl_cepgl.pm.model.user.User;
import org.egl_cepgl.pm.repository.*;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestParam;

import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Blob;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Year;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class ProcurementService {

    private final ProcurementRepository repository;
    private final FileRepository fileRepository;
    private final ApplicantRepository applicantRepository;
    private final EnterpriseRepository enterpriseRepository;
    private final AppFileNotificationRepository appFileNotifRepo;
    private final EntFileNotificationRepository entFileNotifRepo;

    private final RabbitTemplate rabbitTemplate;

    @Value("${rabbitmq.exchange.email.name}")
    private String emailExchange;

    @Value("${rabbitmq.binding.email.name}")
    private String emailRoutingKey;

    @Value("${image-dir}")
    private String imageDir;

    private final FileService fileService;

    @Transactional
    public Procurement save(Procurement obj)
    {
        Procurement procurement_s= this.repository.save(obj);
        procurement_s.getFiles().addAll(obj.getFiles());
        return this.repository.save(procurement_s);
    }

    @Transactional
    public List<Long> addCandidates(AppProDto obj)
    {
        AppProDto appProDto= new AppProDto();
        Procurement procurement_s= this.repository.findById(obj.getProcurement_id()).get();
        Set<Applicant> applicants= new HashSet<>();
        Set<Enterprise> enterprises= new HashSet<>();
        List<Long> idsIn= new ArrayList<>();

        List<File> nfiles= procurement_s.getFiles().stream().filter(f -> f.getTo_notify() == true && f.getNotified() == false).collect(Collectors.toList());
        for(Long aid: obj.getCandidate_ids())
        {
            if(procurement_s.getCandidat_type().equals("APP"))
            {
                applicantRepository.findById(aid).ifPresent((a)-> {
                    applicants.add(a);
                    idsIn.add(a.getId());
                    if(nfiles.size() > 0){
                        sendEmail(a, nfiles, "Nouveaux documents ajoutés/New documents added");
                    }
                });
            }else{
                enterpriseRepository.findById(aid).ifPresent((e)-> {
                    enterprises.add(e);
                    idsIn.add(e.getId());
                    if(nfiles.size() > 0){
                        sendEmail(e, nfiles, "Nouveaux documents ajoutés/New documents added");
                    }
                });
            }
        }
        if(procurement_s.getCandidat_type().equals("APP"))
            procurement_s.setApplicants(applicants);
        else
            procurement_s.setEnterprises(enterprises);

        this.repository.save(procurement_s).getId();
        return idsIn;
    }

    public Page<Procurement> findAll(String search, int page, int size, Boolean dateRange, Date bdate, Date edate)
    {
        Pageable paging = PageRequest.of(page, size);
        Page<Procurement> pageProcurements;
        if (search == null && bdate == null) {
            pageProcurements = this.repository.findAll(paging);
        }else{
            SimpleDateFormat fd= new SimpleDateFormat("YYYY-MM-dd");
            if(dateRange == null)
                pageProcurements = this.repository.findAllByFilters(search, paging);
            else{
                if(edate == null)
                    pageProcurements = this.repository.findAllByFilters2(search, paging, fd.format(bdate));
                else
                    pageProcurements = this.repository.findAllByFilters3(search, paging, fd.format(bdate), fd.format(edate));
            }
        }
        return pageProcurements;
    }

    public List<ProcurementCandidateDto> findAllByIds(Collection<Long> pro_ids)
    {
        List<ProcurementCandidateDto> procurements= this.repository.findAllByIdIsIn(pro_ids).stream().map(ProcurementCandidateDto::fromEntity).collect(Collectors.toList());
        return procurements;
    }

    @Transactional
    public Procurement update(Procurement obj) throws Exception
    {
        Optional<Procurement> p= repository.findById(obj.getId());
        if(p.isPresent()){
            p.get().setNamep(obj.getNamep());
            p.get().setDescription(obj.getDescription());
            p.get().setEntry_date(obj.getEntry_date());
            p.get().setExpiry_date(obj.getExpiry_date());
            p.get().setProject(obj.getProject());
            p.get().setCategory(obj.getCategory());
            p.get().setCandidat_type(obj.getCandidat_type());
            p.get().setStatus(obj.getStatus());
            p.get().setCandidate_auto_apply(obj.getCandidate_auto_apply());
        }
        List<File> fs= p.get().getFiles().stream().filter(f -> f.getTo_notify() == true).collect(Collectors.toList());
        if(fs.size() > 0){
            notifyCandidates(p.get(), fs);
        }
        p.get().getFiles().stream().map( f -> {
            f.setNotified(true);
            return f;
        }).collect(Collectors.toList());
        p.get().getFiles().clear();
        p.get().getFiles().addAll(obj.getFiles());
        return repository.save(p.get());
    }

    public void delete(Long id)
    {
        if(id == null){
            log.error("ID est null");
            return;
        }
        repository.deleteById(id);
    }

    public void deleteCandidates(Long procurement_id, Long candidate_id)
    {
        Procurement procurement_s= this.repository.findById(procurement_id).get();
        if(procurement_s.getCandidat_type().equals("APP"))
            procurement_s.getApplicants().remove(applicantRepository.findById(candidate_id).get());
        else
            procurement_s.getEnterprises().remove(enterpriseRepository.findById(candidate_id).get());

        repository.save(procurement_s);
    }

    private void notifyCandidates(Procurement p, List<File> files)
    {
        if(p.getCandidat_type().equals("APP")){
            if(p.getApplicants() != null){
                for (Applicant a: p.getApplicants()) {
                    sendEmail(a, files, "Nouveaux documents ajoutés/New documents added");
                }
            }
        }else{
            if(p.getEnterprises() != null){
                for (Enterprise e: p.getEnterprises()) {
                    sendEmail(e, files, "Nouveaux documents ajoutés/New documents added");
                }
            }
        }
    }

    private void sendEmail(Object candidate, List<File> files, String subject)
    {
        List<String> fls= new ArrayList<String>();
        List<Long> fids= new ArrayList<Long>();
        String cand_name= (candidate instanceof Applicant)? ((Applicant) candidate).getFirst_name()+' '+((Applicant) candidate).getLast_name() :
                                                            ((Enterprise) candidate).getNamep();
        String cand_email= (candidate instanceof Applicant)? ((Applicant) candidate).getEmail() : ((Enterprise) candidate).getEmail();
        if (files.size() > 0) {
            for (File file : files) {
                if(candidate instanceof Applicant){
                    if(!this.appFileNotifRepo.existsByApplicantAndFile(((Applicant) candidate).getId(), file.getId())){
                        Path filePath = fileService.loadFileAsResource(file.getNamep());
                        fls.add("<p><a href='"+filePath+"'>"+file.getNamep()+"</a></p>");
                        fids.add(file.getId());
                    }
                }else{
                    if(!this.entFileNotifRepo.existsByEnterpriseAndFile(((Enterprise) candidate).getId(), file.getId())){
                        Path filePath = fileService.loadFileAsResource(file.getNamep());
                        fls.add("<p><a href='"+filePath+"'>"+file.getNamep()+"</a></p>");
                        fids.add(file.getId());
                    }
                }
            }
        }
        Map<String, Object> mailData = Map.of(
                "logoEGL",
                imageDir+"/Logo-EGL.jpg",
                "candidate",
                cand_name,
                "files", fls,
                "copyright","© 1970 - "+ Year.now().getValue()+" EGL. Tous droits réservés."
        );
        String[] to= new String[1];
        to[0]= cand_email;
        EmailDetailDTO email= new EmailDetailDTO(to, subject, mailData, "notify_about_file_added");
        if(fids.size() > 0){
            rabbitTemplate.convertAndSend(emailExchange, emailRoutingKey, email);
        }
        if(candidate instanceof Applicant){
            Long cid= ((Applicant) candidate).getId();
            for (Long i: fids){
                this.appFileNotifRepo.save(new AppFileNotification(cid, i));
            }
        }else{
            Long cid= ((Enterprise) candidate).getId();
            for (Long i: fids){
                this.entFileNotifRepo.save(new EntFileNotification(cid, i));
            }
        }
    }
}