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.config.DateConfig;
import com.izouma.wenlvju.domain.*;
import com.izouma.wenlvju.domain.performance.*;
import com.izouma.wenlvju.dto.*;
import com.izouma.wenlvju.enums.CompetitionGroup;
import com.izouma.wenlvju.enums.PerformanceStatus;
import com.izouma.wenlvju.enums.ProgrammeStatus;
import com.izouma.wenlvju.enums.SignedIn;
import com.izouma.wenlvju.exception.BusinessException;
import com.izouma.wenlvju.repo.*;
import com.izouma.wenlvju.repo.performance.*;
import com.izouma.wenlvju.service.ArtTypeService;
import com.izouma.wenlvju.service.UserService;
import com.izouma.wenlvju.service.storage.StorageService;
import com.izouma.wenlvju.utils.FileUtils;
import com.izouma.wenlvju.utils.JpaUtils;
import com.izouma.wenlvju.utils.ObjUtils;
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.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;
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.Charset;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.regex.Pattern;
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;
    private RateAuditRepo           rateAuditRepo;
    private UserService             userService;
    private AwardRepo               awardRepo;


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

    public ProgrammeDTO getByAdmin(@PathVariable Long id) {
        Programme programme = programmeRepo.findById(id).orElseThrow(new BusinessException("无记录"));
        String performance = performanceRepo.findById(programme.getPerformanceId())
                .orElseThrow(new BusinessException("无活动"))
                .getName();
        String organization = organizationRepo.findById(programme.getOrganizationId())
                .orElseThrow(new BusinessException("无承办单位"))
                .getName();
        String gradeOrganization = gradingOrganizationRepo.findById(programme.getGradingOrganizationId())
                .orElseThrow(new BusinessException("无考级机构"))
                .getName();
        String specialty = artTypeRepo.findById(programme.getSpecialtyId())
                .orElseThrow(new BusinessException("无专业"))
                .getName();
        String setting = "";
        if (ObjectUtil.isNotNull(programme.getLevelSettingId())) {
            setting = settingRepo.findById(programme.getLevelSettingId())
                    .orElseThrow(new BusinessException("无分组"))
                    .getName();
        }

        return this.toDTO(programme, performance, organization, gradeOrganization, specialty, setting);
    }

    public Programme save(Programme record, Long userId) {
        if (record.getId() != null) {
            Programme orig = programmeRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
//            if (ProgrammeStatus.INITIAL.equals(orig.getProgrammeStatus())) {
//                Performance performance = performanceRepo.findById(orig.getPerformanceId())
//                        .orElseThrow(new BusinessException("无展演活动"));
//                if (LocalDate.now().isAfter(performance.getEndDate())) {
//                    throw new BusinessException("活动已报名结束！");
//                }
//            }
            ObjUtils.merge(orig, record);
            orig.setParentSpecialtyId(artTypeService.getParent(orig.getSpecialtyId()));
            record = programmeRepo.save(orig);
        } else {
//            Performance performance = performanceRepo.findById(record.getPerformanceId())
//                    .orElseThrow(new BusinessException("无展演活动"));
//            if (LocalDate.now().isAfter(performance.getEndDate())) {
//                throw new BusinessException("活动已报名结束！");
//            }
            record.setParentSpecialtyId(artTypeService.getParent(record.getSpecialtyId()));
            record = programmeRepo.save(record);
        }
        if (ProgrammeStatus.SUBMIT.equals(record.getProgrammeStatus())) {
            rateAuditRepo.save(RateAudit.builder()
                    .rateId(record.getId())
                    .programmeAudit(true)
                    .userId(userId)
                    .remark("已上报节目信息")
                    .status(ProgrammeStatus.SUBMIT.toString())
                    .build());
        }
        return record;
    }

    public Page<Programme> all2(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;
        return 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));
    }


    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.findAllByPublishTrue()
                .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));
        Map<Long, String> awardMap = awardRepo.findAll()
                .stream()
                .collect(Collectors.toMap(Award::getId, Award::getName));

        return all.map(programme -> {
            ProgrammeDTO dto = new ProgrammeDTO(programme);
            dto.setPerformance(performanceMap.get(programme.getPerformanceId()));
            dto.setOrganization(organizationMap.get(programme.getOrganizationId()));
            dto.setGradingOrganization(gradingOrganizationMap.get(programme.getGradingOrganizationId()));
            dto.setSpecialty(artTypeMap.get(programme.getSpecialtyId()));
            if (ObjectUtil.isNotNull(programme.getLevelSettingId())) {
                dto.setLevel(settingMap.get(programme.getLevelSettingId()));
            }
            if (ObjectUtil.isNotNull(programme.getAwardId())) {
                dto.setAward(awardMap.get(programme.getAwardId()));
            }
            return dto;
        });
    }

    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);
        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 dto = new ProgrammeDTO(programme);
                dto.setPerformance(performanceMap.get(programme.getPerformanceId()));
                dto.setOrganization(organizationMap.get(programme.getOrganizationId()));
                dto.setGradingOrganization(gradingOrganizationMap.get(programme.getGradingOrganizationId()));
                dto.setSpecialty(artTypeMap.get(programme.getSpecialtyId()));
                if (ObjectUtil.isNotNull(programme.getLevelSettingId())) {
                    dto.setLevel(settingMap.get(programme.getLevelSettingId()));
                }
                dtos.add(dto);
            } else {
                participants.forEach(participant -> {
                    ProgrammeDTO dto = new ProgrammeDTO(programme);
                    dto.setPerformance(performanceMap.get(programme.getPerformanceId()));
                    dto.setOrganization(organizationMap.get(programme.getOrganizationId()));
                    dto.setGradingOrganization(gradingOrganizationMap.get(programme.getGradingOrganizationId()));
                    dto.setSpecialty(artTypeMap.get(programme.getSpecialtyId()));
                    if (ObjectUtil.isNotNull(programme.getLevelSettingId())) {
                        dto.setLevel(settingMap.get(programme.getLevelSettingId()));
                    }
                    dto.setParticipant(participant);
                    dtos.add(dto);
                });
            }
        });
        return dtos;
    }

    @Transactional(rollbackOn = Exception.class)
    public void upload(MultipartFile file, Long userId) throws Exception {
        Organization organization = organizationRepo.findByUserId(userId).orElseThrow(new BusinessException("无记录"));
//        Long performance = performanceRepo.findNow(LocalDate.now());
//        if (ObjectUtil.isNull(performance)) {
//            throw new BusinessException("没有正在报名中的展演活动");
//        }

        // 临时开启
        Long performance = performanceRepo.findByYear(String.valueOf(LocalDate.now().getYear())).getId();

        File destDir = TempFile.createTempDirectory("import");
        try {


            String originalFilename = file.getOriginalFilename();
            boolean zip = Pattern.matches("zip", FilenameUtils.getExtension(originalFilename));
            boolean rar = false;
            if (!zip) {
                rar = Pattern.matches("rar", FilenameUtils.getExtension(originalFilename));
            }
            try {
                if (rar) {
                    FileUtils.unrar(file.getInputStream(), destDir);
                } else if (zip) {
                    try {
                        ZipUtil.unzip(file.getInputStream(), destDir, StandardCharsets.UTF_8);
                    } catch (Exception e) {
                        ZipUtil.unzip(file.getInputStream(), destDir, Charset.forName("gbk"));
                    }
                }
            } catch (Exception e) {
                throw new BusinessException("最能上传zip或者rar压缩包");
            }


            File xlsxFile = FileUtils.findInDir(destDir, null);
            if (xlsxFile == null) {
                return;
            }
            InputStream indicatorStream = new FileInputStream(xlsxFile);
            UploadDataListener<ProgUploadDTO> listener = new UploadDataListener<>();
            List<ProgUploadDTO> dtos = EasyExcel.read(indicatorStream, ProgUploadDTO.class, listener)
                    .sheet()
                    .doReadSync();

            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, ArtType> artTypeMap = artTypeRepo.findAll()
                    .stream()
                    .collect(Collectors.toMap(ArtType::getName, artType -> artType));


            List<Participant> participants = new ArrayList<>();
            Long pid = null;
            for (ProgUploadDTO dto : dtos) {
                if (dto.getName() != null && dto.getSpecialty() != null) {
                    Programme programme = new Programme(dto);
                    ArtType specialty = artTypeMap.get(dto.getSpecialty());
                    programme.setOrganizationId(organization.getId());
                    programme.setGradingOrganizationId(gradeMap.get(dto.getGradingOrganization()));
                    programme.setPerformanceId(performance);

                    // 专业
                    if (ObjectUtil.isNotNull(specialty)) {
                        programme.setSpecialtyId(specialty.getId());
                    } else {
                        throw new BusinessException("专业名称不正确，仔细核对专业表");
                    }

                    // 节目状态
                    programme.setProgrammeStatus(ProgrammeStatus.INITIAL);
                    String level = dto.getLevel();
                    if (StrUtil.isNotBlank(level)) {
                        level = level.substring(0, 2);
                    }
                    if (dto.getCompetitionGroup().equals(CompetitionGroup.SINGLE)) {
                        programme.setLevelSettingId(mapMap.get(3).get(level));
                    } else if (dto.getCompetitionGroup().equals(CompetitionGroup.COLLECTIVE)) {
                        programme.setLevelSettingId(mapMap.get(4).get(level));
                    }

                    File uploadFile = FileUtils.findInDir(destDir, dto.getName());
                    if (dto.getVideo() != null) {
                        uploadFile = FileUtils.findInDir(destDir, dto.getVideo());
                    }
                    if (ObjectUtil.isNotNull(uploadFile) && uploadFile.exists()) {
                        // 视频
                        if (ObjectUtil.isNotNull(specialty)) {
                            if (!specialty.getCode().startsWith("03")) {
                                // 上传
                                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.setSrc(url);
                                programme.setVideo(vo);
                            } else {
                                // 图片
                                programme.setAnnex(this.saveImg(uploadFile));
                            }
                        }


                    }

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

                Participant participant = new Participant(dto);
                if (ObjectUtil.isNotNull(dto.getParticipantBirthday())) {
                    if (dto.getParticipantBirthday().contains("/")) {
                        try {
                            LocalDate birthday = LocalDate.parse(dto.getParticipantBirthday(), DateTimeFormatter.ofPattern(DateConfig.DEFAULT_DATE_FORMAT1));
                            participant.setBirthday(birthday);
                        } catch (Exception e) {
                            throw new BusinessException("日期格式错误");
                        }

                    } else if (dto.getParticipantBirthday().contains("-")) {
                        try {
                            LocalDate birthday = LocalDate.parse(dto.getParticipantBirthday(), DateTimeFormatter.ofPattern(DateConfig.DEFAULT_DATE_FORMAT));
                            participant.setBirthday(birthday);
                        } catch (Exception e) {
                            throw new BusinessException("日期格式错误");
                        }
                    } else {
                        throw new BusinessException("日期格式错误！");
                    }


                }

                participant.setProgrammeId(pid);
                participant.setPerformanceId(performance);
                participants.add(participant);
                File uploadImg;
                if (dto.getImg() != null) {
//                File uploadFile = new File(destDir, dto.getImg());
                    uploadImg = FileUtils.findInDir(destDir, dto.getImg());
                } else {
                    uploadImg = FileUtils.findInDir(destDir, dto.getIdNo() + "证件照");
                }
                if (ObjectUtil.isNotNull(uploadImg) && uploadImg.exists()) {
                    participant.setImg(this.saveImg(uploadImg));
                }

                File uploadCertificate;
                if (dto.getCertificate() != null) {
                    uploadCertificate = FileUtils.findInDir(destDir, dto.getCertificate());
                } else {
                    uploadCertificate = FileUtils.findInDir(destDir, dto.getIdNo() + "证书");
                }
                if (ObjectUtil.isNotNull(uploadCertificate) && uploadCertificate.exists()) {
                    participant.setCertificate(this.saveImg(uploadCertificate));
                }
            }

            participantRepo.saveAll(participants);
        } catch (Exception e) {
            throw e;
        } finally {
            FileUtils.delFiles(destDir);

        }
    }

    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();
        InputStream is = getClass().getResourceAsStream("/templates/Programme.xlsx");
        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(List<Programme> byPerformanceId, Arrange arrange) {
        boolean flag = true;

        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;
    }

    @Transactional(rollbackOn = Exception.class)
    public Map<String, String> group1(List<Programme> byPerformanceId, Arrange arrange) {
        // 是否继续分组
        boolean flag = true;

        int quantity = arrange.getQuantity();
        if (byPerformanceId.size() < quantity) {
            flag = false;
        }

        List<Programme> result = new ArrayList<>(quantity);
        if (CollUtil.isNotEmpty(arrange.getSpecialtyId())) {
            List<Programme> programmeList = byPerformanceId.stream()
                    .filter(programme -> arrange.getSpecialtyId().contains(programme.getParentSpecialtyId()))
                    .collect(Collectors.toList());

            int size = programmeList.size();
            // 数量小于 ，未填仅限选择或仅限选择为false
            if (size < quantity) {
                if (ObjectUtil.isNull(arrange.getSelectedOnly()) || !arrange.getSelectedOnly()) {
                    programmeList.addAll(byPerformanceId.stream()
                            .filter(programme -> !arrange.getSpecialtyId().contains(programme.getParentSpecialtyId()))
                            .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, int times) {
        List<Long> ids = programmes.stream().map(Programme::getId).collect(Collectors.toList());
        if (times > 1) {
            programmeRepo.setReviewArrangeId(arrangeId, ids);
            return;
        }
        programmeRepo.setArrangeId(arrangeId, ids);
    }

    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, Long arrangeId) {
        Programme programme = programmeRepo.findById(id).orElseThrow(new BusinessException("无记录"));
        Arrange arrange = arrangeRepo.findById(arrangeId).orElseThrow(new BusinessException("无分组"));
        if (arrange.getAuditTimes() > 1) {
            programme.setReviewArrangeId(null);
            programmeRepo.save(programme);
            // 此分组的专业，是否要移除专业
            List<Long> specialtyIds = programmeRepo.findAllByReviewArrangeId(id)
                    .stream()
                    .map(Programme::getParentSpecialtyId)
                    .distinct()
                    .collect(Collectors.toList());
            arrange.setSpecialtyId(specialtyIds);
            arrange.setQuantity(arrange.getQuantity() - 1);
            arrangeRepo.save(arrange);
            return;
        }

        programme.setArrangeId(null);
        programmeRepo.save(programme);
        // 此分组的专业，是否要移除专业
        List<Long> specialtyIds = programmeRepo.findAllByArrangeId(arrangeId)
                .stream()
                .map(Programme::getParentSpecialtyId)
                .distinct()
                .collect(Collectors.toList());
        specialtyIds.remove(null);
        arrange.setSpecialtyId(specialtyIds);
        arrange.setQuantity(arrange.getQuantity() - 1);
        arrangeRepo.save(arrange);
    }

    /*
    增加节目
     */
    public void add(Long id, Long arrangeId) {
        Arrange arrange = arrangeRepo.findById(arrangeId).orElseThrow(new BusinessException("无记录"));
        Programme programme = programmeRepo.findById(id).orElseThrow(new BusinessException("无记录"));
        if (arrange.getAuditTimes() > 1) {
            programme.setReviewArrangeId(arrangeId);
        } else {
            programme.setArrangeId(arrangeId);
        }
        programmeRepo.save(programme);

        arrange.setQuantity(arrange.getQuantity() + 1);
        List<Long> specialtyId = arrange.getSpecialtyId();
        specialtyId.remove(null);
        if (!specialtyId.contains(programme.getParentSpecialtyId())) {
            specialtyId.add(programme.getParentSpecialtyId());
        }
        arrangeRepo.save(arrange);
    }

    /*
    未分组节目
     */
    public List<ProgrammeShowDTO> ungrouped(Long performanceId) {
        Performance performance = performanceRepo.findById(performanceId).orElseThrow(new BusinessException("无展演"));
        if (performance.getAuditTimes() > 1) {
            List<Programme> programmes = programmeRepo.findAllByPerformanceIdAndProgrammeStatusAndReviewArrangeIdIsNull(performanceId, ProgrammeStatus.SUBMIT);
            if (CollUtil.isNotEmpty(programmes)) {
                this.sort(programmes);
                return this.toShowDTOList(programmes);
            }
            return null;
        }
        List<Programme> programmes = programmeRepo.findAllByPerformanceIdAndProgrammeStatusAndArrangeIdIsNull(performanceId, ProgrammeStatus.SUBMIT);
        if (CollUtil.isNotEmpty(programmes)) {
            this.sort(programmes);
            return this.toShowDTOList(programmes);
        }
        return null;
    }

    public List<ProgrammeShowDTO> toShowDTOList(List<Programme> programmes) {
        Map<Long, String> settingMap = settingRepo.findAllByFlagIn(CollUtil.newArrayList(3, 4))
                .stream()
                .collect(Collectors.toMap(Setting::getId, Setting::getName));

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

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

        Set<Long> ids = programmes.stream().map(Programme::getOrganizationId).collect(Collectors.toSet());
        Map<Long, String> organizationMap = organizationRepo.findAllById(ids)
                .stream()
                .collect(Collectors.toMap(Organization::getId, Organization::getName));

        return programmes.stream().map(programme -> {
            ProgrammeShowDTO dto = new ProgrammeShowDTO(programme);
            dto.setLevel(settingMap.get(programme.getLevelSettingId()));
            dto.setGradingOrganization(gradeMap.get(programme.getGradingOrganizationId()));
            dto.setOrganization(organizationMap.get(programme.getOrganizationId()));
            dto.setSpecialty(artTypeMap.get(programme.getSpecialtyId()));
            return dto;
        }).collect(Collectors.toList());
    }

    /*
    查看按分组查看节目
     */
    public Page<ArrangeProgrammeDTO> byArrange(PageQuery pageQuery) {
        Map<String, Object> query = pageQuery.getQuery();
        Boolean review = Convert.convert(Boolean.class, query.get("review"));
        query.remove("review");
        if (review) {
            pageQuery.setSort("allSigned,asc;reviewArrangeId,desc;gradingOrganizationId,asc;organizationId,asc;");
        } else {
            pageQuery.setSort("allSigned,asc;arrangeId,desc;gradingOrganizationId,asc;organizationId,asc;");
        }
        Page<Programme> all = programmeRepo.findAll((root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> and = JpaUtils.toPredicates(pageQuery, Programme.class, root, criteriaQuery, criteriaBuilder);
            if (review) {
                and.add(criteriaBuilder.isNotNull(root.get("reviewArrangeId")));
            } else {
                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 -> {
            if (review) {
                return programme.getReviewArrangeId();
            }
            return 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(review ? programme.getReviewArrangeId() : 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, Long userId) {
        Programme programme = programmeRepo.findById(id).orElseThrow(new BusinessException("无节目"));
        programme.setSignedIn(signedIn);
        programmeRepo.save(programme);

        //该状态
        Performance performance = performanceRepo.findById(programme.getPerformanceId())
                .orElseThrow(new BusinessException("无活动"));
        if (PerformanceStatus.ARRANGE_JUDGE.equals(performance.getStatus())) {
            performance.setStatus(PerformanceStatus.SIGN_IN);
            performanceRepo.save(performance);
        }

        long unSigned = programmeRepo.countAllByArrangeIdAndSignedInIsNull(programme.getArrangeId());
        if (unSigned == 0) {
            programmeRepo.allSigned(programme.getArrangeId());
        }
        if (SignedIn.SIGNED_IN.equals(signedIn) && StrUtil.isEmpty(description)) {
            description = "已签到";
        }
        rateAuditRepo.save(RateAudit.builder()
                .userId(userId)
                .status(PerformanceStatus.SIGN_IN.toString())
                .remark(description)
                .build());
    }

    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> byScore(PageQuery pageQuery) {
        // 是否复审
        Map<String, Object> query = pageQuery.getQuery();
        Integer auditTimes = Convert.convert(Integer.class, query.get("auditTimes"));
        query.remove("auditTimes");

        Long userId = Convert.convert(Long.class, query.get("userId"));
        query.remove("userId");

        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;
        List<Long> arrangeId = arrangeJudgeRepo.findArrangeIdByExpertIdAndAuditTimes(userId, auditTimes);
        if (CollUtil.isEmpty(arrangeId)) {
            return new PageImpl<>(new ArrayList<>(), JpaUtils.toPageRequest(pageQuery), 0);
        }

        pageQuery.setSort("arrangeId,asc;id,asc;");
        Page<Programme> all = programmeRepo.findAll(((root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> and = JpaUtils.toPredicates(pageQuery, Programme.class, root, criteriaQuery, criteriaBuilder);
            if (auditTimes > 1) {
                and.add(root.get("reviewArrangeId").in(arrangeId));
            } else {
                and.add(root.get("arrangeId").in(arrangeId));
            }
            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));

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

    public Page<ProgScoreAdminDTO> toDTOPage(Page<Programme> all) {
        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, Double> scoreMap = programmeScoreRepo.findAllByProgrammeIdIn(ids)
                .stream()
                .collect(Collectors.groupingBy(ProgrammeScore::getProgrammeId, Collectors.averagingDouble(ProgrammeScore::getScore)));
        Map<Long, String> gradeMap = gradingOrganizationRepo.findAll()
                .stream()
                .collect(Collectors.toMap(GradingOrganization::getId, GradingOrganization::getName));
        Map<Long, String> organizationMap = organizationRepo.findAll()
                .stream()
                .collect(Collectors.toMap(Organization::getId, Organization::getName));
        return all.map(programme -> {
            ProgScoreAdminDTO dto = new ProgScoreAdminDTO(programme);
            dto.setSpecialty(artTypeMap.get(programme.getSpecialtyId()));
            dto.setMyScore(scoreMap.get(programme.getId()));
            dto.setGradingOrganization(gradeMap.get(programme.getGradingOrganizationId()));
            dto.setOrganization(organizationMap.get(programme.getOrganizationId()));
            if (CollUtil.isNotEmpty(participantMap.get(programme.getId()))) {
                List<String> participants = participantMap.get(programme.getId())
                        .stream()
                        .map(Participant::getName)
                        .collect(Collectors.toList());
                dto.setParticipant(participants);
            }

            return dto;
        });
    }

    public 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));

        List<Arrange> arranges = arrangeRepo.findAllById(arrangeId);
        Map<Long, Arrange> arrangeMap = arranges
                .stream()
                .collect(Collectors.toMap(Arrange::getId, arrange -> arrange));

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

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

        return all.map(programme -> {
            ProgrammeScoreDTO dto = new ProgrammeScoreDTO(programme);
            dto.setSpecialty(artTypeMap.get(programme.getSpecialtyId()));
            dto.setLevelSetting(settingMap.get(programme.getLevelSettingId()));
            // 初审/复审
            Arrange arrange = arranges.get(0);
            dto.setArrangeName(arrangeMap.get(arrange.getAuditTimes() > 1 ? programme.getReviewArrangeId() : programme.getArrangeId())
                    .getName());

            ProgrammeScore programmeScore = scoreMap.get(programme.getId());
            if (ObjectUtil.isNotNull(programmeScore)) {
                dto.setMyScore(programmeScore.getScore());
                dto.setRemark(programmeScore.getRemark());
                dto.setSecond(programmeScore.isSecond());
            }
            if (CollUtil.isNotEmpty(participantMap.get(programme.getId()))) {
                List<String> participants = participantMap.get(programme.getId())
                        .stream()
                        .map(Participant::getName)
                        .collect(Collectors.toList());
                dto.setParticipant(participants);
            }

            return dto;
        });
    }

    /*
    第一次审核
     */
    public void firstAudit(Long id, ProgrammeStatus status, String remark, Long userId) {
        Programme programme = programmeRepo.findById(id).orElseThrow(new BusinessException("无节目"));
        programme.setProgrammeStatus(status);
        programmeRepo.save(programme);
        rateAuditRepo.save(RateAudit.builder()
                .status(status.toString())
                .rateId(id)
                .programmeAudit(true)
                .remark(remark)
                .userId(userId)
                .build());
    }

    public ProgrammeShowDTO toShowDTO(Programme programme) {
        ProgrammeShowDTO dto = new ProgrammeShowDTO(programme);
        List<ParticipantDTO> participants = participantRepo.findAllByProgrammeId(programme.getId())
                .stream()
                .map(ParticipantDTO::new)
                .collect(Collectors.toList());
        dto.setParticipants(participants);
        organizationRepo.findById(programme.getOrganizationId()).ifPresent(og -> dto.setOrganization(og.getName()));
        gradingOrganizationRepo.findById(programme.getGradingOrganizationId())
                .ifPresent(go -> dto.setGradingOrganization(go.getName()));
        artTypeRepo.findById(programme.getSpecialtyId()).ifPresent(at -> dto.setSpecialty(at.getName()));
        settingRepo.findById(programme.getLevelSettingId()).ifPresent(st -> dto.setLevel(st.getName()));
        return dto;
    }

    /*
    查分数信息
     */
    public ProgrammeShowDTO get(Long id, String phone) {
        Programme apply = programmeRepo.findById(id).orElseThrow(new BusinessException("无记录"));
        userService.getAuth(id, phone, apply.getPhone());
        ProgrammeShowDTO showDTO = this.toShowDTO(apply);
        awardRepo.findById(apply.getAwardId()).ifPresent(aw -> showDTO.setAward(aw.getName()));
        performanceRepo.findById(apply.getPerformanceId()).ifPresent(pe -> showDTO.setShowAward(pe.isShowAward()));
        return showDTO;
    }

    /*
    进入复审
     */
    public void review(Long performanceId, int pass, Long userId) {
        List<Programme> programmes = programmeRepo.findAllByPerformanceIdAndProgrammeStatus(performanceId, ProgrammeStatus.SUBMIT);
        long count = programmes.stream().filter(programme -> ObjectUtil.isNull(programme.getScore())).count();
        if (count > 0) {
            throw new BusinessException("有节目未评分");
        }
        programmes.sort(Comparator.comparingDouble(Programme::getScore));
        List<Programme> eliminate = programmes.subList(pass, programmes.size());
        List<RateAudit> rateAudits = new ArrayList<>();
        eliminate.forEach(programme -> {
            programme.setProgrammeStatus(ProgrammeStatus.REVIEW_FAILED);
            rateAudits.add(RateAudit.builder()
                    .rateId(programme.getId())
                    .programmeAudit(true)
                    .userId(userId)
                    .remark("未进入复审")
                    .status(ProgrammeStatus.REVIEW_FAILED.toString())
                    .build());
        });

        programmeRepo.saveAll(eliminate);
        rateAuditRepo.saveAll(rateAudits);
    }

    public Page<ProgrammeResultDTO> result(PageQuery pageQuery) {

        Page<Programme> all = this.all2(pageQuery);
        List<Programme> programmes = all.getContent();

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

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

        Set<Long> ids = programmes.stream().map(Programme::getOrganizationId).collect(Collectors.toSet());
        Map<Long, String> organizationMap = organizationRepo.findAllById(ids)
                .stream()
                .collect(Collectors.toMap(Organization::getId, Organization::getName));

        Map<Long, String> awardMap = awardRepo.findAll()
                .stream()
                .collect(Collectors.toMap(Award::getId, Award::getName));

        return all.map(programme -> {
            ProgrammeResultDTO dto = new ProgrammeResultDTO(programme);
            dto.setAward(awardMap.get(programme.getAwardId()));
            dto.setOrganization(organizationMap.get(programme.getOrganizationId()));
            dto.setGradingOrganization(organizationMap.get(programme.getGradingOrganizationId()));
            dto.setSpecialty(artTypeMap.get(programme.getSpecialtyId()));
            dto.setGradingOrganization(gradeMap.get(programme.getGradingOrganizationId()));
            dto.setCompetitionGroup(programme.getCompetitionGroup().equals(CompetitionGroup.SINGLE) ? "个人" : "集体");
            return dto;
        });
    }
}
