package com.izouma.wenlvju.service.performance;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.exception.ExcelAnalysisException;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.fastjson.JSONObject;
import com.izouma.wenlvju.domain.ArtType;
import com.izouma.wenlvju.domain.User;
import com.izouma.wenlvju.domain.performance.Arrange;
import com.izouma.wenlvju.domain.performance.ArrangeJudge;
import com.izouma.wenlvju.domain.performance.Performance;
import com.izouma.wenlvju.domain.performance.Programme;
import com.izouma.wenlvju.dto.ArrangeDTO;
import com.izouma.wenlvju.dto.PageQuery;
import com.izouma.wenlvju.dto.ProgrammeDTO;
import com.izouma.wenlvju.enums.PerformanceStatus;
import com.izouma.wenlvju.enums.ProgrammeStatus;
import com.izouma.wenlvju.exception.BusinessException;
import com.izouma.wenlvju.repo.ArtTypeRepo;
import com.izouma.wenlvju.repo.UserRepo;
import com.izouma.wenlvju.repo.performance.ArrangeJudgeRepo;
import com.izouma.wenlvju.repo.performance.ArrangeRepo;
import com.izouma.wenlvju.repo.performance.PerformanceRepo;
import com.izouma.wenlvju.repo.performance.ProgrammeRepo;
import com.izouma.wenlvju.utils.JpaUtils;
import com.izouma.wenlvju.utils.excel.UploadDataListener;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.persistence.criteria.Predicate;
import javax.transaction.Transactional;
import java.io.IOException;
import java.io.InputStream;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@Service
@Slf4j
@AllArgsConstructor
public class ArrangeService {

    private ArrangeRepo      arrangeRepo;
    private ProgrammeService programmeService;
    private ArtTypeRepo      artTypeRepo;
    private ProgrammeRepo    programmeRepo;
    private ArrangeJudgeRepo arrangeJudgeRepo;
    private UserRepo         userRepo;
    private PerformanceRepo  performanceRepo;

    public Page<Arrange> all(PageQuery pageQuery) {
        Map<Long, String> artMap = artTypeRepo.findAll()
                .stream()
                .collect(Collectors.toMap(ArtType::getId, ArtType::getName));
        return arrangeRepo.findAll(JpaUtils.toSpecification(pageQuery, Arrange.class), JpaUtils.toPageRequest(pageQuery))
                .map(arrange -> {
                    arrange.setSpecialtyName(arrange.getSpecialtyId()
                            .stream()
                            .map(artMap::get)
                            .collect(Collectors.toList()));
                    return arrange;
                });
    }

    public Page<Arrange> all2(PageQuery pageQuery) {
        Map<Long, String> artMap = artTypeRepo.findAll()
                .stream()
                .collect(Collectors.toMap(ArtType::getId, ArtType::getName));
        List<ArrangeJudge> ajs = arrangeJudgeRepo.findAll();
        Set<Long> userIds = ajs.stream().map(ArrangeJudge::getExpertId).collect(Collectors.toSet());
        Map<Long, List<ArrangeJudge>> judgeMap = ajs
                .stream()
                .collect(Collectors.groupingBy(ArrangeJudge::getArrangeId));
        Map<Long, String> userMap = userRepo.findAllByIdInAndDelFalse(userIds)
                .stream()
                .collect(Collectors.toMap(User::getId, User::getNickname));
        return arrangeRepo.findAll(JpaUtils.toSpecification(pageQuery, Arrange.class), JpaUtils.toPageRequest(pageQuery))
                .map(arrange -> {
                    arrange.setSpecialtyName(arrange.getSpecialtyId()
                            .stream()
                            .map(artMap::get)
                            .collect(Collectors.toList()));
                    List<ArrangeJudge> arrangeJudges = judgeMap.get(arrange.getId());
                    if (CollUtil.isNotEmpty(arrangeJudges)) {
                        String experts = arrangeJudges.stream()
                                .map(arrangeJudge -> userMap.get(arrangeJudge.getExpertId()))
                                .collect(Collectors.joining(","));
                        arrange.setExperts(experts);
                    }

                    return arrange;
                });
    }

    public void chooseGroup(ArrangeDTO dto) {
        Performance performance = performanceRepo.findById(dto.getPerformanceId())
                .orElseThrow(new BusinessException("无展演活动"));
        if (LocalDate.now().isBefore(performance.getEndDate())) {
            throw new BusinessException("活动报名未结束");
        }
        PerformanceStatus status = performance.getStatus();
        // 第一次分组
        if (PerformanceStatus.UNDER_REVIEW.equals(status)) {
            performance.setStatus(PerformanceStatus.ARRANGE);
            status = PerformanceStatus.ARRANGE;
            if (performance.getAuditTimes() == 0) {
                performance.setAuditTimes(1);
            }

            performanceRepo.save(performance);
        }

        if (!PerformanceStatus.ARRANGE.equals(status)) {
            throw new BusinessException("活动分组已发布");
        }
        boolean online = performance.isOnline();
        if (performance.getAuditTimes() > 1) {
            online = performance.getReviewOnline();
        }
        if (online) {
            this.groupOnline(dto, performance.getAuditTimes());
            return;
        }
        this.group(dto, performance.getAuditTimes());
    }

    /*
    分组
     */
    public void group(ArrangeDTO dto, int times) {
        long total = 0;
        int days = Period.between(dto.getStartDate(), dto.getEndDate()).getDays();
        for (int i = 0; i <= days; i++) {
            if (dto.isMorning()) {
                total += Duration.between(dto.getMorningStartTime(), dto.getMorningEndTime()).toMinutes();
            }
            if (dto.isAfternoon()) {
                total += Duration.between(dto.getAfternoonStartTime(), dto.getAfternoonEndTime()).toMinutes();
            }
        }

        int time = dto.getQuantity() * dto.getDuration();
        // 分组数量 = 总时长/节目数量*单个节目时长+10
        int group = (int) Math.ceil((double) total / (time + 10));

        LocalDate date = dto.getStartDate();
        DateTimeFormatter format = DateTimeFormatter.ofPattern("MM月dd日");
        String[] arr = {"一", "二", "三", "四", "五", "六", "七", "八", "九", "十"};
        String format1 = format.format(date);

        List<Programme> byPerformanceId = programmeRepo.findAllByPerformanceIdAndProgrammeStatus(dto.getPerformanceId(), ProgrammeStatus.SUBMIT);

        if (dto.isMorning() && dto.isAfternoon()) {
            this.saveArrange(byPerformanceId, dto, group, time, format1, arr, times);
        } else {
            LocalTime startAt;
            LocalTime endAt;
            LocalTime startTime;
            boolean morning = true;
            String amPm = "上午";

            if (dto.isMorning()) {
                startTime = dto.getMorningStartTime();
                startAt = dto.getMorningStartTime();
                endAt = dto.getMorningEndTime();
            } else if (dto.isAfternoon()) {
                startTime = dto.getAfternoonStartTime();
                morning = false;
                startAt = dto.getAfternoonStartTime();
                endAt = dto.getAfternoonEndTime();
                amPm = "下午";
            } else {
                throw new BusinessException("时间安排不能为空");
            }
            int j = 0;
            for (int i = 0; i < group; i++) {
                LocalTime endTime = startTime.plusMinutes(time);

                String name = format1 + amPm + "第" + arr[j] + "场";
                Arrange arrange = Arrange.builder()
                        .address(dto.getAddress())
                        .duration(dto.getDuration())
                        .performanceId(dto.getPerformanceId())
                        .quantity(dto.getQuantity())
                        .date(date)
                        .startTime(startTime)
                        .endTime(endTime)
                        .duration(dto.getDuration())
                        .specialtyId(dto.getSpecialtyId())
                        .name(name)
                        .morning(morning)
                        .auditTimes(times)
                        .selectedOnly(dto.getSelectedOnly())
                        .build();

                Map<String, String> programmes = programmeService.group1(byPerformanceId, arrange);
                List<Programme> programmeList = JSONObject.parseArray(programmes.get("programmes"), Programme.class);
                List<Long> ids = programmeList.stream()
                        .map(Programme::getParentSpecialtyId)
                        .distinct()
                        .collect(Collectors.toList());
                arrange.setSpecialtyId(ids);
                arrange.setQuantity(programmeList.size());
                arrange = arrangeRepo.save(arrange);

                programmeService.saveAll(programmeList, arrange.getId(), times);
                byPerformanceId.removeAll(programmeList);

                // 插入节目
                if (!Boolean.parseBoolean(programmes.get("flag"))) {
                    //不需要这么多分组
                    break;
                }

                j++;
                startTime = endTime.plusMinutes(10);
                if (startTime.isAfter(endAt)) {
                    date = date.plusDays(1);
                    j = 0;
                    startTime = startAt;
                }
            }

        }

    }


    public void saveArrange(List<Programme> byPerformanceId, ArrangeDTO dto, int group, int time, String format1, String[] arr, int times) {
        LocalDate date = dto.getStartDate();
        LocalTime startTime = dto.getMorningStartTime();
        boolean morning = true;
        int j = 0;

        for (int i = 0; i < group; i++) {
            LocalTime endTime = startTime.plusMinutes(time);
            String amPm = morning ? "上午" : "下午";
            String name = format1 + amPm + "第" + arr[j] + "场";
            Arrange arrange = Arrange.builder()
                    .address(dto.getAddress())
                    .duration(dto.getDuration())
                    .performanceId(dto.getPerformanceId())
                    .quantity(dto.getQuantity())
                    .date(date)
                    .startTime(startTime)
                    .endTime(endTime)
                    .duration(dto.getDuration())
                    .specialtyId(dto.getSpecialtyId())
                    .name(name)
                    .morning(morning)
                    .auditTimes(times)
                    .build();

            Map<String, String> programmes = programmeService.group1(byPerformanceId, arrange);
            List<Programme> programmeList = JSONObject.parseArray(programmes.get("programmes"), Programme.class);
            List<Long> ids = programmeList.stream()
                    .map(Programme::getParentSpecialtyId)
                    .distinct()
                    .collect(Collectors.toList());
            arrange.setSpecialtyId(ids);
            arrange.setQuantity(programmeList.size());
            arrange = arrangeRepo.save(arrange);

            programmeService.saveAll(programmeList, arrange.getId(), times);
            byPerformanceId.removeAll(programmeList);

            // 插入节目
            if (!Boolean.parseBoolean(programmes.get("flag"))) {
                //不需要这么多分组
                break;
            }
            j++;
            startTime = endTime.plusMinutes(10);
            if (startTime.isAfter(dto.getMorningEndTime()) && startTime.isBefore(dto.getAfternoonStartTime())) {
                startTime = dto.getAfternoonStartTime();
                j = 0;
                morning = false;
            } else if (startTime.isAfter(dto.getAfternoonEndTime())) {
                date = date.plusDays(1);
                startTime = dto.getMorningStartTime();
                j = 0;
                morning = true;
            }
        }
    }

    /*
    展演形式为线上进行分组
    不需要时间、上下午、地点
     */
    public void groupOnline(ArrangeDTO dto, int times) {
        List<Programme> byPerformanceId = programmeRepo.findAllByPerformanceIdAndProgrammeStatus(dto.getPerformanceId(), ProgrammeStatus.SUBMIT);
        int group = (int) Math.ceil((double) byPerformanceId.size() / dto.getQuantity());

        long count = arrangeRepo.countArrangeByPerformanceId(dto.getPerformanceId()) + 1;
        for (int i = 0; i < group; i++) {
            Arrange arrange = Arrange.builder()
                    .performanceId(dto.getPerformanceId())
                    .quantity(dto.getQuantity())
                    .specialtyId(dto.getSpecialtyId())
                    .name("第" + count + "场")
                    .auditTimes(times)
                    .build();

            Map<String, String> programmes = programmeService.group1(byPerformanceId, arrange);
            List<Programme> programmeList = JSONObject.parseArray(programmes.get("programmes"), Programme.class);
            List<Long> ids = programmeList.stream()
                    .map(Programme::getParentSpecialtyId)
                    .distinct()
                    .collect(Collectors.toList());
            ids.remove(null);
            arrange.setSpecialtyId(ids);
            arrange.setQuantity(programmeList.size());
            arrange = arrangeRepo.save(arrange);

            programmeService.saveAll(programmeList, arrange.getId(), times);

            byPerformanceId.removeAll(programmeList);
            // 插入节目
            if (!Boolean.parseBoolean(programmes.get("flag"))) {
                //不需要这么多分组
                break;
            }
            count++;
        }
    }

    public void publish(Long performanceId) {
        Performance performance = performanceRepo.findById(performanceId).orElseThrow(new BusinessException("无展演"));
        boolean online = performance.isOnline();
        if (performance.getAuditTimes() > 1) {
            online = performance.getReviewOnline();
        }
        // 改为分配评委中
        performance.setStatus(PerformanceStatus.ARRANGE_JUDGE);
        performanceRepo.save(performance);
        if (!online) {
            this.publishOffline(performanceId);
        }

        //记录状态
//        List<Long> pids = programmeRepo.findIdByPerformanceIdAndProgrammeStatus(performanceId, ProgrammeStatus.SUBMIT);
//        List<RateAudit> rateAudits = new ArrayList<>();
//        pids.forEach(id -> rateAudits.add(RateAudit.builder()
//                .status(PerformanceStatus.ARRANGE.toString())
//                .userId(userId)
//                .programmeAudit(true)
//                .rateId(id)
//                .remark("分组安排已发布")
//                .build()));
//        rateAuditRepo.saveAll(rateAudits);
    }

    public void publishOffline(Long performanceId) {
        // 分布发布
//        arrangeRepo.publish(performanceId);
        Map<Long, Arrange> arranges = arrangeRepo.findAllByPerformanceId(performanceId)
                .stream()
                .collect(Collectors.toMap(Arrange::getId, arrange -> arrange));
        List<Programme> programmeList = programmeRepo.findAllByPerformanceIdAndProgrammeStatus(performanceId, ProgrammeStatus.SUBMIT);
        Map<Long, List<Programme>> programmes = programmeList
                .stream()
                .collect(Collectors.groupingBy(Programme::getArrangeId));
        // 节目改状态，加上表演时间
        programmes.forEach((k, v) -> {
            Arrange arrange = arranges.get(k);
            LocalDate date = arrange.getDate();
            LocalTime start = arrange.getStartTime();
            int duration = arrange.getDuration();
            programmeService.sort(v);
            final LocalDateTime[] beginAt = {LocalDateTime.of(date, start)};
            v.forEach(p -> {
                LocalDateTime endAt = beginAt[0].plusMinutes(duration);
                p.setShowBegin(beginAt[0]);
                p.setShowEnd(endAt);
                p.setAddress(arrange.getAddress());
                // 待签到
//                p.setProgrammeProcess(ProgrammeProcess.SIGN_IN);
                beginAt[0] = endAt;
            });
        });
        programmeRepo.saveAll(programmeList);
    }

    public Page<Arrange> byExpert(PageQuery pageQuery, Long userId) {
        List<Long> arrangeId = arrangeJudgeRepo.findArrangeIdByExpertId(userId);
        if (CollUtil.isEmpty(arrangeId)) {
            return new PageImpl<>(new ArrayList<>(), JpaUtils.toPageRequest(pageQuery), 0);
        }
        Map<Long, String> artTypeMap = artTypeRepo.findAllByParentIsNull()
                .stream()
                .collect(Collectors.toMap(ArtType::getId, ArtType::getName));
        pageQuery.setSort("date,asc;startTime,asc;");
        return arrangeRepo.findAll(((root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> and = JpaUtils.toPredicates(pageQuery, Arrange.class, root, criteriaQuery, criteriaBuilder);
            and.add(root.get("id").in(arrangeId));
            return criteriaBuilder.and(and.toArray(new Predicate[0]));
        }), JpaUtils.toPageRequest(pageQuery)).map(arrange -> {
            List<String> names = arrange.getSpecialtyId().stream().map(artTypeMap::get).collect(Collectors.toList());
            arrange.setSpecialtyName(names);
            return arrange;
        });
    }

    @Transactional(rollbackOn = Exception.class)
    public List<ProgrammeDTO> upload(MultipartFile file) {

        try {
            InputStream fis = file.getInputStream();
            UploadDataListener<ProgrammeDTO> listener = new UploadDataListener<>();
            return EasyExcel.read(fis, ProgrammeDTO.class, listener)
                    .sheet()
                    .doReadSync();


        } catch (ExcelAnalysisException e) {
            e.printStackTrace();
            if (e.getCause() instanceof ExcelDataConvertException) {
                ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) e.getCause();
                String cellMsg = "";
                String errorMsg = String.format("excel表格:第%s行,第%s列,数据值为:%s,该数据值不符合要求,请检验后重新导入!请检查其他的记录是否有同类型的错误!", excelDataConvertException.getRowIndex() + 1, excelDataConvertException.getColumnIndex(), cellMsg);
                log.error(errorMsg);
                throw new BusinessException(errorMsg);
            }

        } catch (IOException e) {
            log.error("上传失败", e);
            throw new BusinessException("上传失败", e.getMessage());
        }
        return null;
    }

    public void saveProgramme(MultipartFile file) {

        List<ProgrammeDTO> programmeDTOS = this.upload(file);
        if (CollUtil.isEmpty(programmeDTOS)) {
            return;
        }

        Performance performance = performanceRepo.findByYear(String.valueOf(LocalDate.now().getYear()));
        if (LocalDate.now().isBefore(performance.getEndDate())) {
            throw new BusinessException("活动报名未结束");
        }
        PerformanceStatus status = performance.getStatus();
        // 第一次分组
        if (PerformanceStatus.UNDER_REVIEW.equals(status)) {
            // 直接分配评委
            performance.setStatus(PerformanceStatus.ARRANGE_JUDGE);
            if (performance.getAuditTimes() == 0) {
                performance.setAuditTimes(1);
            }

            performanceRepo.save(performance);
        }
        // 节目id
        List<Long> ids = programmeDTOS.stream().map(ProgrammeDTO::getId).distinct().collect(Collectors.toList());
        List<Programme> programmes = programmeRepo.findAllById(ids);
        // 专业
        List<Long> specialtyId = programmes.stream()
                .map(Programme::getParentSpecialtyId)
                .distinct()
                .collect(Collectors.toList());
        specialtyId.remove(null);
        long count = arrangeRepo.countArrangeByPerformanceId(performance.getId()) + 1;
        // 新建分组
        Arrange arrange = arrangeRepo.save(Arrange.builder()
                .name("第" + count + "组")
                .auditTimes(1)
                .specialtyId(specialtyId)
                .quantity(programmes.size())
                .performanceId(performance.getId())
                .auditTimes(performance.getAuditTimes())
                .build());

        // 保存分组
        programmeRepo.setArrangeId(arrange.getId(), ids);

        // 分配评委
        // 查找名字
        String originalFilename = file.getOriginalFilename();
        if (StrUtil.isEmpty(originalFilename)) {
            throw new BusinessException("文件名为空！");
        }
        int end = originalFilename.lastIndexOf(".");
        String orginalName = originalFilename.substring(0, end);
        User user = userRepo.findByPhoneAndDelFalse(orginalName);
        if (ObjectUtil.isNull(user)) {
            throw new BusinessException("无评委");
        }
        // 保存评委
        arrangeJudgeRepo.save(ArrangeJudge.builder()
                .arrangeId(arrange.getId())
                .expertId(user.getId())
                .auditTimes(arrange.getAuditTimes())
                .build());
    }
}
