package com.hz.pm.api.expert.manage;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.hz.pm.api.common.model.constant.BizConst;
import com.hz.pm.api.common.model.entity.KeyValDTO;
import com.hz.pm.api.common.util.BizUtils;
import com.hz.pm.api.expert.assembler.ExpertInfoCmdAssembler;
import com.hz.pm.api.expert.entity.ExpertUserFullInfo;
import com.hz.pm.api.expert.helper.ExpertManageHelper;
import com.hz.pm.api.expert.model.ExpertBasicInfo;
import com.hz.pm.api.expert.model.ExpertEduInfo;
import com.hz.pm.api.expert.model.ExpertJobInfo;
import com.hz.pm.api.expert.model.ExpertProfessionalInfo;
import com.hz.pm.api.expert.model.cmd.ExpertFullInfoSaveCmd;
import com.hz.pm.api.expert.model.dto.DictionaryVO;
import com.hz.pm.api.expert.model.req.ExpertRegistrationRequest;
import com.hz.pm.api.expert.model.req.ExpertUserBasicInfoSubmitRequest;
import com.hz.pm.api.expert.model.vo.ExpertDetailVO;
import com.hz.pm.api.expert.model.vo.ExpertPortraitVO;
import com.hz.pm.api.expert.service.ExpertInfoService;
import com.hz.pm.api.expert.service.IExpertUserFullInfoService;
import com.hz.pm.api.external.MhExpertQrCodeGenClient;
import com.hz.pm.api.external.MhFileClient;
import com.hz.pm.api.external.model.dto.MhFileInfoDTO;
import com.hz.pm.api.external.model.vo.MhFileInfoVO;
import com.hz.pm.api.meeting.entity.domain.MeetingExpertJudge;
import com.hz.pm.api.meeting.entity.dto.ExpertInvitedRecordDTO;
import com.hz.pm.api.meeting.entity.enumeration.ExpertAttendStatusEnum;
import com.hz.pm.api.meeting.entity.enumeration.ExpertJudgeEnum.AttendStatus;
import com.hz.pm.api.meeting.entity.enumeration.ExpertJudgeEnum.Performance;
import com.hz.pm.api.meeting.service.IMeetingExpertJudgeService;
import com.hz.pm.api.meeting.service.IMeetingExpertService;
import com.hz.pm.api.meta.constant.ExpertDictTypeEnum;
import com.hz.pm.api.meta.helper.DictionaryCache;
import com.hz.pm.api.meta.model.dto.DictionaryDTO;
import com.hz.pm.api.meta.model.entity.ExpertDictionary;
import com.hz.pm.api.meta.service.IExpertDictionaryService;
import com.hz.pm.api.sms.constant.VerificationCodeType;
import com.hz.pm.api.sms.helper.VerifyCodeCheckHelper;
import com.hz.pm.api.sys.model.entity.Role;
import com.hz.pm.api.sys.model.entity.UserRole;
import com.hz.pm.api.sys.service.IRoleService;
import com.hz.pm.api.sys.service.IUserRoleService;
import com.hz.pm.api.user.model.entity.UserInfo;
import com.hz.pm.api.user.model.enumeration.RoleEnum;
import com.hz.pm.api.user.model.enumeration.UserAvailableEnum;
import com.hz.pm.api.user.service.IUserInfoService;
import com.hz.pm.api.user.util.LoginUserUtil;
import com.ningdatech.basic.exception.BizException;
import com.ningdatech.basic.model.GenericResult;
import com.ningdatech.basic.util.CollUtils;
import com.ningdatech.zwdd.client.ZwddClient;
import com.ningdatech.zwdd.model.dto.DingInfoByMobileDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

/**
 * @author liuxinxin
 * @date 2023/2/23 上午8:55
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class ExpertManage {

    private final ExpertManageHelper expertManageHelper;
    private final IExpertUserFullInfoService expertUserFullInfoService;
    private final ExpertInfoService expertInfoService;
    private final IUserInfoService userInfoService;
    private final ExpertMetaApplyManage expertMetaApplyManage;
    private final IUserRoleService userRoleService;
    private final IRoleService roleService;
    private final VerifyCodeCheckHelper verifyCodeCheckHelper;
    private final ZwddClient zwddClient;
    private final IMeetingExpertService meetingExpertService;
    private final IMeetingExpertJudgeService expertJudgeService;
    private final IExpertDictionaryService expertDictionaryService;
    private final DictionaryCache dictionaryCache;
    private final MhExpertQrCodeGenClient expertQrCodeGenClient;
    private final MhFileClient mhFileClient;


    @Value("${expert-registration.verify-code.check:true}")
    private Boolean expertRegistrationVerifyCodeCheck;

    private Map<String, List<DictionaryVO>> listExpertDictionary(Long userId) {
        List<ExpertDictionary> dicts = expertDictionaryService.listByUserId(Collections.singleton(userId));
        return dicts.stream().map(w -> {
            Optional<DictionaryDTO> dict = dictionaryCache.getByCode(w.getDictionaryCode());
            if (dict.isPresent()) {
                DictionaryDTO dto = dict.get();
                return new DictionaryVO(dto.getDictionaryCode(), dto.getName(), dto.getDictionaryType());
            } else {
                return null;
            }
        }).filter(Objects::nonNull).collect(Collectors.groupingBy(DictionaryVO::getDictionaryType));
    }

    /**
     * 专家管理员新增专家
     *
     * @param req /
     */
    @Transactional(rollbackFor = Exception.class)
    public void expertBasicInfoSubmit(ExpertUserBasicInfoSubmitRequest req) {
        Long operatorId = LoginUserUtil.getUserId();
        // 用户id
        Long userId = assemblerAndGenerateExpert(req, operatorId);
        // 批量通过专家入库审核/专家履职意向审核
        expertMetaApplyManage.autoPassExpertApply(userId);

        // 增加用户专家角色
        Role role = roleService.getOne(Wrappers.lambdaQuery(Role.class).eq(Role::getCode, RoleEnum.EXPERT.name()));
        UserRole expertRole = userRoleService.getOne(userId, role.getId());
        if (Objects.isNull(expertRole)) {
            expertRole = new UserRole();
            expertRole.setUserId(userId);
            expertRole.setRoleId(role.getId());
            userRoleService.save(expertRole);
        }
    }

    public ExpertDetailVO getExpertDetail(Long userId, boolean getFileDetail) {
        ExpertUserFullInfo eui = expertUserFullInfoService.getByUserId(userId);
        if (eui == null) {
            throw BizException.wrap("专家信息不存在");
        }
        ExpertDetailVO expertDetail = BeanUtil.copyProperties(eui, ExpertDetailVO.class);
        expertDetail.setDictionaries(listExpertDictionary(userId));
        boolean isTechExpert = false;
        List<DictionaryVO> expertType = expertDetail.getDictionaries().get(ExpertDictTypeEnum.EXPERT_TYPE.getKey());
        if (expertType != null) {
            isTechExpert = expertType.get(0).getDictionaryName().contains("技术");
        }
        String expertQrCode = expertQrCodeGenClient.genExpertQrCode(eui.getMhExpertId());
        expertDetail.setExpertQrCodeBase64(expertQrCode);
        expertDetail.setIsTechExpert(isTechExpert);
        if (getFileDetail) {
            processExpertFiles(expertDetail);
        }
        return expertDetail;
    }

    private void processExpertFiles(ExpertDetailVO expertDetail) {
        List<String> fileIdList = new ArrayList<>();
        BizUtils.notBlank(expertDetail.getIdCardFile(), fileIdList::add);
        BizUtils.notBlank(expertDetail.getAvatarFile(), fileIdList::add);
        BizUtils.notBlank(expertDetail.getRecommendFile(), fileIdList::add);
        BizUtils.notBlank(expertDetail.getDegreeCertFile(), fileIdList::add);
        BizUtils.notBlank(expertDetail.getGraduationCertFile(), fileIdList::add);
        BizUtils.notBlank(expertDetail.getSocialInsureFile(), fileIdList::add);
        BizUtils.notBlank(expertDetail.getItaicCredentials(), fileIdList::add);
        BizUtils.notBlank(expertDetail.getMajorCredentials(), fileIdList::add);
        String fileIds = String.join(",", fileIdList);
        List<MhFileInfoDTO> fileInfoList = mhFileClient.listFileInfo(fileIds);
        if (CollUtil.isEmpty(fileInfoList)) {
            return;
        }
        Map<String, MhFileInfoDTO> fileMap = CollUtils.listToMap(fileInfoList, MhFileInfoDTO::getFileId);

        BiFunction<String, Map<String, MhFileInfoDTO>, List<MhFileInfoVO>> function = (str, map) -> {
            String[] split = str.split(",");
            return Arrays.stream(split).map(w -> {
                MhFileInfoDTO fileInfoDto = map.get(w);
                return BeanUtil.copyProperties(fileInfoDto, MhFileInfoVO.class);
            }).filter(Objects::nonNull).collect(Collectors.toList());
        };

        BizUtils.notBlank(expertDetail.getIdCardFile(), w -> {
            List<MhFileInfoVO> files = function.apply(w, fileMap);
            expertDetail.setIdCardFileDetail(files);
        });
        BizUtils.notBlank(expertDetail.getAvatarFile(), w -> {
            List<MhFileInfoVO> files = function.apply(w, fileMap);
            expertDetail.setAvatarFileDetail(files);
        });
        BizUtils.notBlank(expertDetail.getRecommendFile(), w -> {
            List<MhFileInfoVO> files = function.apply(w, fileMap);
            expertDetail.setRecommendFileDetail(files);
        });
        BizUtils.notBlank(expertDetail.getDegreeCertFile(), w -> {
            List<MhFileInfoVO> files = function.apply(w, fileMap);
            expertDetail.setDegreeCertFileDetail(files);
        });
        BizUtils.notBlank(expertDetail.getGraduationCertFile(), w -> {
            List<MhFileInfoVO> files = function.apply(w, fileMap);
            expertDetail.setGraduationCertFileDetail(files);
        });
        BizUtils.notBlank(expertDetail.getSocialInsureFile(), w -> {
            List<MhFileInfoVO> files = function.apply(w, fileMap);
            expertDetail.setSocialInsureFileDetail(files);
        });
        BizUtils.notBlank(expertDetail.getItaicCredentials(), w -> {
            List<MhFileInfoVO> files = function.apply(w, fileMap);
            expertDetail.setItaicCredentialsDetail(files);
        });
        BizUtils.notBlank(expertDetail.getMajorCredentials(), w -> {
            List<MhFileInfoVO> files = function.apply(w, fileMap);
            expertDetail.setMajorCredentialsDetail(files);
        });

    }

    public Long generateOrGetUserId(ExpertBasicInfo basicInfo, Long operatorId) {
        String phoneNo = basicInfo.getPhoneNo();
        UserInfo userInfo = userInfoService.getUserInfoByPhoneNo(phoneNo);
        if (Objects.isNull(userInfo)) {
            GenericResult<DingInfoByMobileDTO> dingInfoByMobile = zwddClient.getDingInfoByMobile(basicInfo.getPhoneNo());
            if (dingInfoByMobile.isSuccess()) {
                DingInfoByMobileDTO data = dingInfoByMobile.getData();
                String accountId = data.getAccountId();
                userInfo = userInfoService.getUserInfoByAccountId(Long.parseLong(accountId));
            }
            if (userInfo == null) {
                userInfo = UserInfo.builder()
                        .username(basicInfo.getName())
                        .realName(basicInfo.getName())
                        .available(UserAvailableEnum.DISABLE.name())
                        .mobile(phoneNo)
                        .createBy(operatorId)
                        .updateBy(operatorId)
                        .createOn(LocalDateTime.now())
                        .updateOn(LocalDateTime.now())
                        .build();
                userInfoService.save(userInfo);
            } else if (!StrUtil.equals(userInfo.getRealName(), basicInfo.getName())) {
                log.warn("相似账户为：{} ==> {}", basicInfo.getPhoneNo(), userInfo);
                throw BizException.wrap("已存在同手机号，但不同名的账号");
            } else if (StrUtil.isBlank(userInfo.getMobile())) {
                userInfo.setMobile(basicInfo.getPhoneNo());
                userInfoService.updateById(userInfo);
            }
        }
        ExpertUserFullInfo one = expertUserFullInfoService.getOne(Wrappers.lambdaQuery(ExpertUserFullInfo.class)
                .eq(ExpertUserFullInfo::getPhoneNo, phoneNo));
        if (Objects.nonNull(one) && !one.getUserId().equals(userInfo.getId())) {
            throw new BizException("该手机号已被专家注册，请确认后再填写");
        }
        return userInfo.getId();
    }

    /**
     * 社会专家报名 需要校验手机号
     *
     * @param request \
     */
    @Transactional(rollbackFor = Exception.class)
    public void expertRegistration(ExpertRegistrationRequest request) {
        String verificationCode = request.getVerificationCode();
        String phoneNo = request.getBasicInfo().getPhoneNo();
        //校验 专家验证码
        if (Boolean.TRUE.equals(expertRegistrationVerifyCodeCheck)) {
            verifyCodeCheckHelper.verification(VerificationCodeType.EXPERT_REGISTER, phoneNo, verificationCode);
        }
        assemblerAndGenerateExpert(request, null);
    }


    public Long assemblerAndGenerateExpert(ExpertUserBasicInfoSubmitRequest req, Long operatorId) {
        if (Objects.isNull(operatorId)) {
            operatorId = -1L;
        }
        ExpertBasicInfo basicInfo = req.getBasicInfo();
        Long userId = generateOrGetUserId(basicInfo, operatorId);

        // 校验区域编码合法性 校验履职意向编码合法性
        ExpertEduInfo eduInfo = req.getEduInfo();
        ExpertJobInfo jobInfo = req.getJobInfo();
        ExpertProfessionalInfo professionalInfo = req.getProfessionalInfo();
        // 校验标签字段
        expertManageHelper.tagFieldCheck(professionalInfo, basicInfo);
        // 校验字典字段
        expertManageHelper.dictionaryFieldCheck(basicInfo, eduInfo, jobInfo);
        // 判断专家提交状态，判断是否可以进行此操作
        ExpertUserFullInfo expertUserFullInfo = expertUserFullInfoService.getByUserId(userId);

        boolean submitBasicInfoStatusEnable = Objects.isNull(expertUserFullInfo);
        if (submitBasicInfoStatusEnable) {
            // 新建 保存
            ExpertFullInfoSaveCmd expertFullInfoSaveCmd = ExpertInfoCmdAssembler
                    .buildExpertFullInfoSaveCmd(userId, basicInfo, eduInfo, jobInfo, professionalInfo);
            expertInfoService.saveExpertInfo(expertFullInfoSaveCmd);
        }
        return userId;
    }

    public ExpertPortraitVO expertPortrait(Long expertId, Integer year) {
        ExpertPortraitVO portrait = ExpertPortraitVO.init();
        LocalDate firstDate = LocalDate.of(year, 1, 1);
        LocalDateTime sTime = firstDate.atStartOfDay();
        LocalDateTime eTime = firstDate.with(TemporalAdjusters.lastDayOfYear()).atTime(LocalTime.MAX);
        List<ExpertInvitedRecordDTO> meetings = meetingExpertService.listByExpertIdAndTime(expertId, sTime, eTime);
        portrait.setReceivedCount(meetings.size());
        if (!meetings.isEmpty()) {
            AtomicInteger agreedCount = new AtomicInteger(0);
            Map<Integer, Long> countByPerMonth = meetings.stream()
                    .filter(w -> {
                        boolean agreed = ExpertAttendStatusEnum.AGREED.eq(w.getStatus());
                        if (agreed) {
                            agreedCount.incrementAndGet();
                        }
                        return agreed;
                    }).collect(Collectors.groupingBy(w -> w.getCreateOn().getMonthValue(), Collectors.counting()));
            portrait.setAttendCountPerMonth(new ArrayList<>());
            for (int i = 1; i <= Month.DECEMBER.getValue(); i++) {
                KeyValDTO<Integer, Long> currMonth = KeyValDTO.of(i, countByPerMonth.getOrDefault(i, 0L));
                portrait.getAttendCountPerMonth().add(currMonth);
            }
            portrait.setConfirmedCount(agreedCount.get());
            List<Long> meetingExpertIds = CollUtils.fieldList(meetings, ExpertInvitedRecordDTO::getMeetingExpertId);
            List<MeetingExpertJudge> expertJudges = expertJudgeService.listByMeetingExpertIds(meetingExpertIds);
            if (!expertJudges.isEmpty()) {
                int positivity = 0;
                for (MeetingExpertJudge expertJudge : expertJudges) {
                    if (AttendStatus.BE_LATE.eq(expertJudge.getAttended())) {
                        portrait.incrBeLateCount();
                    }
                    if (Boolean.TRUE.equals(expertJudge.getLeaveEarly())) {
                        portrait.incrLeaveEarlyCount();
                    }
                    if (Boolean.TRUE.equals(expertJudge.getBrokeRule())) {
                        portrait.incrViolationCount();
                    }
                    if (Performance.POSITIVE.eq(expertJudge.getPerformance())) {
                        positivity++;
                    }
                }
                portrait.setPositivity((int) Math.round(positivity * BizConst.D100 / expertJudges.size()));
            }

        }
        return portrait;
    }

}
