package com.izouma.wenlvju.service.performance;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSONObject;
import com.izouma.wenlvju.domain.*;
import com.izouma.wenlvju.domain.performance.*;
import com.izouma.wenlvju.dto.ArrangeProgrammeDTO;
import com.izouma.wenlvju.dto.PageQuery;
import com.izouma.wenlvju.dto.ProgrammeDTO;
import com.izouma.wenlvju.dto.ProgrammeScoreDTO;
import com.izouma.wenlvju.enums.CompetitionGroup;
import com.izouma.wenlvju.enums.SignedIn;
import com.izouma.wenlvju.exception.BusinessException;
import com.izouma.wenlvju.repo.ArtTypeRepo;
import com.izouma.wenlvju.repo.GradingOrganizationRepo;
import com.izouma.wenlvju.repo.OrganizationRepo;
import com.izouma.wenlvju.repo.SettingRepo;
import com.izouma.wenlvju.repo.performance.*;
import com.izouma.wenlvju.service.ArtTypeService;
import com.izouma.wenlvju.service.storage.StorageService;
import com.izouma.wenlvju.utils.FileUtils;
import com.izouma.wenlvju.utils.JpaUtils;
import com.izouma.wenlvju.utils.excel.UploadDataListener;
import lombok.AllArgsConstructor;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.poi.util.TempFile;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.persistence.criteria.Predicate;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.Transactional;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
@AllArgsConstructor
public class ProgrammeService {

    private ProgrammeRepo           programmeRepo;
    private ParticipantRepo         participantRepo;
    private GradingOrganizationRepo gradingOrganizationRepo;
    private OrganizationRepo        organizationRepo;
    private ArtTypeRepo             artTypeRepo;
    private SettingRepo             settingRepo;
    private ArtTypeService          artTypeService;
    private PerformanceRepo         performanceRepo;
    private ArrangeRepo             arrangeRepo;
    private ProgrammeScoreRepo      programmeScoreRepo;
    private ArrangeJudgeRepo        arrangeJudgeRepo;
    private StorageService          storageService;


    public Page<Programme> all(PageQuery pageQuery) {
        return programmeRepo.findAll(JpaUtils.toSpecification(pageQuery, Programme.class), JpaUtils.toPageRequest(pageQuery));
    }

    public Page<ProgrammeDTO> backAll(PageQuery pageQuery) {
        Map<String, Object> query = pageQuery.getQuery();
        Object code = query.get("code");
        String artCode = null;
        if (ObjectUtil.isNotNull(code)) {
            artCode = Convert.convert(String.class, code);
            query.remove("code");
        }

        List<ArtType> artTypes = artTypeRepo.findAll();

        String finalArtCode = artCode;
        Page<Programme> all = programmeRepo.findAll(((root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> and = JpaUtils.toPredicates(pageQuery, Programme.class, root, criteriaQuery, criteriaBuilder);
            if (StrUtil.isNotBlank(finalArtCode)) {
                and.add(root.get("specialtyId").in(artTypeService.getIds(artTypes, finalArtCode)));
            }
            return criteriaBuilder.and(and.toArray(new Predicate[0]));

        }), JpaUtils.toPageRequest(pageQuery));

        List<Programme> content = all.getContent();
        if (CollUtil.isEmpty(content)) {
            return all.map(ProgrammeDTO::new);
        }

        List<Long> settingIds = new ArrayList<>();
        content.forEach(programme -> {
            settingIds.add(programme.getLevelSettingId());
        });

        Map<Long, String> performanceMap = performanceRepo.findAll()
                .stream()
                .collect(Collectors.toMap(Performance::getId, Performance::getName));

        Map<Long, String> organizationMap = organizationRepo.findAll()
                .stream()
                .collect(Collectors.toMap(Organization::getId, Organization::getName));
        Map<Long, String> gradingOrganizationMap = gradingOrganizationRepo.findAll()
                .stream()
                .collect(Collectors.toMap(GradingOrganization::getId, GradingOrganization::getName));
        Map<Long, String> artTypeMap = artTypes.stream()
                .collect(Collectors.toMap(ArtType::getId, ArtType::getName));
        Map<Long, String> settingMap = settingRepo.findAllById(settingIds)
                .stream()
                .collect(Collectors.toMap(Setting::getId, Setting::getName));

        return all.map(programme -> toDTO(programme, performanceMap.get(programme.getPerformanceId()),
                organizationMap.get(programme.getOrganizationId()),
                gradingOrganizationMap.get(programme.getGradingOrganizationId()),
                artTypeMap.get(programme.getSpecialtyId()),
                settingMap.get(programme.getLevelSettingId())));
    }

    public ProgrammeDTO toDTO(Programme programme, String performance, String organization, String gradingOrganization, String artType,
                              String setting) {
        ProgrammeDTO dto = new ProgrammeDTO(programme);
        dto.setPerformance(performance);
        dto.setOrganization(organization);
        dto.setGradingOrganization(gradingOrganization);
        dto.setSpecialty(artType);
        dto.setLevel(setting);
//        dto.setQuantity(participant);
        return dto;
    }

    public List<ProgrammeDTO> toDTOList(List<Programme> programmes) {

        List<Long> ids = programmes.stream().map(Programme::getId).collect(Collectors.toList());

        Map<Long, String> performanceMap = performanceRepo.findAll()
                .stream()
                .collect(Collectors.toMap(Performance::getId, Performance::getName));

        Map<Long, String> organizationMap = organizationRepo.findAll()
                .stream()
                .collect(Collectors.toMap(Organization::getId, Organization::getName));
        Map<Long, String> gradingOrganizationMap = gradingOrganizationRepo.findAll()
                .stream()
                .collect(Collectors.toMap(GradingOrganization::getId, GradingOrganization::getName));
        Map<Long, String> artTypeMap = artTypeRepo.findAll()
                .stream()
                .collect(Collectors.toMap(ArtType::getId, ArtType::getName));
        Map<Long, String> settingMap = settingRepo.findAllByFlagIn(CollUtil.newArrayList(3, 4))
                .stream()
                .collect(Collectors.toMap(Setting::getId, Setting::getName));

        Map<Long, List<Participant>> participantMap = participantRepo.findAllByProgrammeIdIn(ids)
                .stream()
                .collect(Collectors.groupingBy(Participant::getProgrammeId));

        List<ProgrammeDTO> dtos = new ArrayList<>();
        programmes.forEach(programme -> {
            List<Participant> participants = participantMap.get(programme.getId());
            if (CollUtil.isEmpty(participants)) {
                ProgrammeDTO programmeDTO = toDTO(programme, performanceMap.get(programme.getPerformanceId()),
                        organizationMap.get(programme.getOrganizationId()),
                        gradingOrganizationMap.get(programme.getGradingOrganizationId()),
                        artTypeMap.get(programme.getSpecialtyId()),
                        settingMap.get(programme.getLevelSettingId()));
                dtos.add(programmeDTO);
            } else {
                participants.forEach(participant -> {
                    ProgrammeDTO programmeDTO = toDTO(programme, performanceMap.get(programme.getPerformanceId()),
                            organizationMap.get(programme.getOrganizationId()),
                            gradingOrganizationMap.get(programme.getGradingOrganizationId()),
                            artTypeMap.get(programme.getSpecialtyId()),
                            settingMap.get(programme.getLevelSettingId()));
                    programmeDTO.setParticipant(participant);
                    dtos.add(programmeDTO);
                });
            }
        });
        return dtos;
    }

    @Transactional(rollbackOn = Exception.class)
    public void upload(MultipartFile file, Long userId) throws IOException {
        Organization organization = organizationRepo.findByUserId(userId).orElseThrow(new BusinessException("无记录"));

        File destDir = TempFile.createTempDirectory("import");
        ZipUtil.unzip(file.getInputStream(), destDir, StandardCharsets.UTF_8);

        File xlsxFile = FileUtils.findExcel1(destDir);
        if (xlsxFile == null) {
            Map<String, File> map = FileUtils.findExcel(destDir);
            if (map == null) return;

            xlsxFile = map.get("file");
            destDir = map.get("destDir");
        }


        InputStream indicatorStream = new FileInputStream(xlsxFile);
        UploadDataListener<ProgrammeDTO> listener = new UploadDataListener<>();
        List<ProgrammeDTO> dtos = EasyExcel.read(indicatorStream, ProgrammeDTO.class, listener).sheet().doReadSync();

        Map<String, Long> performanceMap = performanceRepo.findAll()
                .stream()
                .collect(Collectors.toMap(Performance::getName, Performance::getId));

        Map<Integer, Map<String, Long>> mapMap = settingRepo.findAllByFlagIn(CollUtil.newArrayList(3, 4))
                .stream()
                .collect(Collectors.groupingBy(Setting::getFlag, Collectors.toMap(Setting::getName, Setting::getId)));

        Map<String, Long> gradeMap = gradingOrganizationRepo.findAll()
                .stream()
                .collect(Collectors.toMap(GradingOrganization::getName, GradingOrganization::getId));

        Map<String, Long> artTypeMap = artTypeRepo.findAll()
                .stream()
                .collect(Collectors.toMap(ArtType::getName, ArtType::getId));


        List<Participant> participants = new ArrayList<>();
        Long pid = null;
        for (ProgrammeDTO dto : dtos) {
            if (dto.getName() != null && dto.getSpecialty() != null) {
                Programme programme = new Programme(dto);
                programme.setOrganizationId(organization.getId());
                programme.setGradingOrganizationId(gradeMap.get(dto.getGradingOrganization()));
                programme.setPerformanceId(performanceMap.get(dto.getPerformance()));
                programme.setSpecialtyId(artTypeMap.get(dto.getSpecialty()));
                if (dto.getCompetitionGroup().equals(CompetitionGroup.SINGLE)) {
                    programme.setLevelSettingId(mapMap.get(3).get(dto.getLevel()));
                } else if (dto.getCompetitionGroup().equals(CompetitionGroup.COLLECTIVE)) {
                    programme.setLevelSettingId(mapMap.get(4).get(dto.getLevel()));
                }

                if (dto.getVideo() != null) {
                    File uploadFile = new File(destDir, dto.getVideo());
                    if (uploadFile.exists()) {
                        // 上传
                        String videoPath = "video/" + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date())
                                + RandomStringUtils.randomAlphabetic(8)
                                + "." + FilenameUtils.getExtension(uploadFile.getName());
                        String url = storageService.uploadFromInputStream(new FileInputStream(uploadFile), videoPath);
                        VideoObject vo = new VideoObject();
//                        vo.setPoster(posterUrl);
                        vo.setSrc(url);
                        programme.setVideo(vo);
                    }
                }

                pid = programmeRepo.save(programme).getId();
            }

            Participant participant = new Participant(dto);
            participant.setProgrammeId(pid);
            participants.add(participant);

            if (dto.getImg() != null) {
                File uploadFile = new File(destDir, dto.getImg());
                if (uploadFile.exists()) {
                    participant.setImg(this.saveImg(uploadFile));
                }
            }
            if (dto.getCertificate() != null) {
                File uploadFile = new File(destDir, dto.getCertificate());
                if (uploadFile.exists()) {
                    participant.setCertificate(this.saveImg(uploadFile));
                }
            }
        }

        participantRepo.saveAll(participants);
    }

    private String saveImg(File uploadFile) throws FileNotFoundException {
        String path = "image/" + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date())
                + RandomStringUtils.randomAlphabetic(8)
                + "." + FilenameUtils.getExtension(uploadFile.getName());
        return storageService.uploadFromInputStream(new FileInputStream(uploadFile), path);
    }

    public void excelTemp(HttpServletResponse response) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        File file = new File(this.getClass().getResource("/templates/Programme.xlsx").getFile());
        InputStream is = new FileInputStream(file);

        byte[] buffer = new byte[1024];
        int len;
        while ((len = is.read(buffer)) > -1) {
            outputStream.write(buffer, 0, len);
        }
        outputStream.flush();

        response.setContentType("application/vnd.ms-excel");
        response.setHeader("Content-Disposition", "attachment; filename=" + "Programme.xlsx");

        ServletOutputStream sos = response.getOutputStream();
        sos.write(outputStream.toByteArray());

        sos.flush();
        outputStream.close();
    }

    @Transactional(rollbackOn = Exception.class)
    public Map<String, String> group(Arrange arrange) {
        boolean flag = true;

        List<Programme> byPerformanceId = programmeRepo.findAllByPerformanceIdAndStatus(arrange.getPerformanceId(), 0);
        int quantity = arrange.getQuantity();
        if (byPerformanceId.size() < quantity) {
            flag = false;
        }

        List<Programme> result = new ArrayList<>(quantity);
        if (CollUtil.isNotEmpty(arrange.getSpecialtyId())) {

            List<Long> artTypeChild = artTypeService.getChild(arrange.getSpecialtyId(), new ArrayList<>(arrange.getSpecialtyId()));
            List<Programme> programmeList = byPerformanceId.stream()
                    .filter(programme -> artTypeChild.contains(programme.getSpecialtyId()))
                    .collect(Collectors.toList());

            int size = programmeList.size();
            if (size < quantity) {
                programmeList.addAll(byPerformanceId.stream()
                        .filter(programme -> !artTypeChild.contains(programme.getSpecialtyId()))
                        .limit(quantity - size)
                        .collect(Collectors.toList()));
                flag = false;
            }

            this.sort(programmeList);
            result.addAll(programmeList.stream().limit(quantity).collect(Collectors.toList()));
        } else {
            this.sort(byPerformanceId);
            result.addAll(byPerformanceId.stream().limit(quantity).collect(Collectors.toList()));
        }
        Map<String, String> map = new HashMap<>();
        map.put("flag", Boolean.toString(flag));
        map.put("programmes", JSONObject.toJSONString(result));
        return map;
    }

    public void saveAll(List<Programme> programmes, Long arrangeId) {
        programmes.forEach(programme -> {
            programme.setArrangeId(arrangeId);
            programme.setStatus(1);
        });
        programmeRepo.saveAll(programmes);
    }

    public void sort(List<Programme> programmes) {
        programmes.sort((a, b) -> {
            if (a.getOrganizationId().equals(b.getOrganizationId())) {
                if (a.getOrganizationId().equals(b.getOrganizationId())) {
                    return a.getSpecialtyId().compareTo(b.getSpecialtyId());
                } else {
                    return a.getOrganizationId().compareTo(b.getOrganizationId());
                }
            } else {
                return a.getOrganizationId().compareTo(b.getOrganizationId());
            }
        });
    }


    /*
    移除分组
     */
    public void removeArrange(Long id) {
        Programme programme = programmeRepo.findById(id).orElseThrow(new BusinessException("无记录"));
        Long arrangeId = programme.getArrangeId();
        programme.setStatus(0);
        programme.setArrangeId(null);
        programmeRepo.save(programme);
        arrangeRepo.minusQuantity(arrangeId);
    }

    /*
    增加节目
     */
    public void add(Long id, Long arrangeId) {
//        LocalDateTime showTime = programmeRepo.findFirstByArrangeIdOrderByShowTimeDesc(arrangeId).getShowTime();
        Arrange arrange = arrangeRepo.findById(arrangeId).orElseThrow(new BusinessException("无记录"));
        Programme programme = programmeRepo.findById(id).orElseThrow(new BusinessException("无记录"));
//        programme.setShowTime(showTime.plusMinutes(arrange.getDuration()));
        programme.setArrangeId(arrangeId);
        programme.setStatus(1);
        programmeRepo.save(programme);

        arrange.setQuantity(arrange.getQuantity() + 1);
        arrangeRepo.save(arrange);
    }

    /*
    未分组节目
     */
    public List<Programme> ungrouped(Long performanceId) {
        List<Programme> programmes = programmeRepo.findAllByPerformanceIdAndStatus(performanceId, 0);
        this.sort(programmes);
        return programmes;
    }

    /*
    查看按分组查看节目
     */
    public Page<ArrangeProgrammeDTO> byArrange(PageQuery pageQuery) {
//        Specification<Programme> specification = JpaUtils.toSpecification(pageQuery, Programme.class);
//        specification.and((root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.and(criteriaBuilder.isNull(root.get("arrangeId"))));

        Page<Programme> all = programmeRepo.findAll((root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> and = JpaUtils.toPredicates(pageQuery, Programme.class, root, criteriaQuery, criteriaBuilder);
            and.add(criteriaBuilder.isNotNull(root.get("arrangeId")));
            return criteriaBuilder.and(and.toArray(new Predicate[0]));
        }, JpaUtils.toPageRequest(pageQuery));

        Set<Long> arrangeIds = all.getContent().stream().map(Programme::getArrangeId).collect(Collectors.toSet());
        Map<Long, Arrange> arrangeMap = arrangeRepo.findAllById(arrangeIds)
                .stream()
                .collect(Collectors.toMap(Arrange::getId, arrange -> arrange));
        Map<Long, String> organizationMap = organizationRepo.findAll()
                .stream()
                .collect(Collectors.toMap(Organization::getId, Organization::getName));
        Map<Long, String> gradingOrganizationMap = gradingOrganizationRepo.findAll()
                .stream()
                .collect(Collectors.toMap(GradingOrganization::getId, GradingOrganization::getName));
        return all.map(programme -> {
            ArrangeProgrammeDTO dto = new ArrangeProgrammeDTO(programme);
            Arrange arrange = arrangeMap.get(programme.getArrangeId());
            dto.setArrangeName(arrange.getName());
            dto.setWriterDirector(arrange.getWriterDirector());
            dto.setWriterPhone(arrange.getPhone());

            dto.setOrganization(organizationMap.get(programme.getOrganizationId()));
            dto.setGradingOrganization(gradingOrganizationMap.get(programme.getGradingOrganizationId()));
            return dto;
        });
    }

    /*
    签到
     */
    public void signIn(Long id, SignedIn signedIn, String description) {
        Programme programme = programmeRepo.findById(id).orElseThrow(new BusinessException("无节目"));
        programme.setSignedIn(signedIn);
        programme.setDescription(description);
        programme.setSignedAt(LocalDateTime.now());
        programmeRepo.save(programme);

        long unSigned = programmeRepo.countAllByArrangeIdAndStatusIsNotNull(programme.getArrangeId());
        if (unSigned == 0) {
            programmeRepo.allSigned(programme.getArrangeId());
        }
    }

    public ProgrammeScoreDTO getDTO(Long id, Long userId) {
        Programme programme = programmeRepo.findById(id).orElseThrow(new BusinessException("无节目"));
        ProgrammeScoreDTO dto = new ProgrammeScoreDTO(programme);
        artTypeRepo.findById(programme.getSpecialtyId()).ifPresent(at -> dto.setSpecialty(at.getName()));
        arrangeRepo.findById(programme.getArrangeId()).ifPresent(arr -> dto.setArrangeName(arr.getName()));
        ProgrammeScore programmeScore = programmeScoreRepo.findByProgrammeIdAndExpertId(programme.getId(), userId);
        if (ObjectUtil.isNotNull(programmeScore)) {
            dto.setMyScore(programmeScore.getScore());
            dto.setRemark(programmeScore.getRemark());
            dto.setSecond(programmeScore.isSecond());
        }
        List<String> participants = participantRepo.findAllByProgrammeId(programme.getId())
                .stream()
                .map(Participant::getName)
                .collect(Collectors.toList());
        dto.setParticipant(participants);
        return dto;
    }

    public Page<ProgrammeScoreDTO> byExpert(PageQuery pageQuery, Long userId) {
        pageQuery.setSort("showBegin,asc;");
        Long arrangeId = Convert.convert(Long.class, pageQuery.getQuery().get("arrangeId"));
        return this.toDTOPage(this.all(pageQuery), Collections.singletonList(arrangeId), userId);
    }

    public Page<ProgrammeScoreDTO> byScore(PageQuery pageQuery, Long userId) {
        List<Long> arrangeId = arrangeJudgeRepo.findArrangeIdByExpertId(userId);
        if (CollUtil.isEmpty(arrangeId)) {
            return new PageImpl<>(new ArrayList<>(), JpaUtils.toPageRequest(pageQuery), 0);
        }
        pageQuery.setSort("allScore,asc;showBegin,asc;arrangeId,asc;");
        Page<Programme> all = programmeRepo.findAll(((root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> and = JpaUtils.toPredicates(pageQuery, Programme.class, root, criteriaQuery, criteriaBuilder);
            and.add(root.get("arrangeId").in(arrangeId));
            return criteriaBuilder.and(and.toArray(new Predicate[0]));
        }), JpaUtils.toPageRequest(pageQuery));

        return this.toDTOPage(all, arrangeId, userId);
    }

    private Page<ProgrammeScoreDTO> toDTOPage(Page<Programme> all, Collection<Long> arrangeId, Long userId) {
        List<Long> ids = all.getContent().stream().map(Programme::getId).distinct().collect(Collectors.toList());
        Map<Long, List<Participant>> participantMap = participantRepo.findAllByProgrammeIdIn(ids)
                .stream()
                .collect(Collectors.groupingBy(Participant::getProgrammeId));
        Map<Long, String> artTypeMap = artTypeRepo.findAll()
                .stream()
                .collect(Collectors.toMap(ArtType::getId, ArtType::getName));

        Map<Long, String> arrangeMap = arrangeRepo.findAllById(arrangeId)
                .stream()
                .collect(Collectors.toMap(Arrange::getId, Arrange::getName));

        Map<Long, ProgrammeScore> scoreMap = programmeScoreRepo.findByProgrammeIdInAndExpertId(ids, userId)
                .stream()
                .collect(Collectors.toMap(ProgrammeScore::getProgrammeId, programmeScore -> programmeScore));

        return all.map(programme -> {
            ProgrammeScoreDTO dto = new ProgrammeScoreDTO(programme);
            dto.setSpecialty(artTypeMap.get(programme.getSpecialtyId()));
            dto.setArrangeName(arrangeMap.get(programme.getArrangeId()));
            ProgrammeScore programmeScore = scoreMap.get(programme.getId());
            if (ObjectUtil.isNotNull(programmeScore)) {
                dto.setMyScore(programmeScore.getScore());
                dto.setRemark(programmeScore.getRemark());
                dto.setSecond(programmeScore.isSecond());
            }
            List<String> participants = participantMap.get(programme.getId())
                    .stream()
                    .map(Participant::getName)
                    .collect(Collectors.toList());
            dto.setParticipant(participants);
            return dto;
        });
    }
}
