package com.yida.data.school.visitor.service.impl;

import static java.util.Comparator.comparingInt;

import cc.mrbird.febs.common.redis.service.RedisService;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yida.data.common.core.entity.WxPublicQr;
import com.yida.data.common.core.entity.constant.AppConstant;
import com.yida.data.common.core.entity.constant.CachePrefixConstant;
import com.yida.data.common.core.entity.constant.WxPublicQrTypeConstant;
import com.yida.data.common.core.entity.notice.qywx.BaseStaffNotice;
import com.yida.data.common.core.entity.notice.qywx.inside.Text;
import com.yida.data.common.core.entity.school.visitor.EduVisitorRecord;
import com.yida.data.common.core.entity.system.Dept;
import com.yida.data.common.core.entity.system.EduApp;
import com.yida.data.common.core.entity.system.EduDeptWxPublic;
import com.yida.data.common.core.entity.system.EduWxPublicTemplateMsg;
import com.yida.data.common.core.entity.user.EduStaff;
import com.yida.data.common.core.enums.WxPublicTemplateMsgType;
import com.yida.data.common.core.exception.FebsException;
import com.yida.data.common.core.utils.Asserts;
import com.yida.data.common.core.utils.QrCodeUtil;
import com.yida.data.common.core.utils.WxPublicUtil;
import com.yida.data.common.core.utils.WxUtil;
import com.yida.data.common.core.vo.NameValueVO;
import com.yida.data.common.service.CommonService;
import com.yida.data.school.dto.visitor.ListVisitorRecordDTO;
import com.yida.data.school.dto.visitor.ListVisitorRecordToStaffDTO;
import com.yida.data.school.visitor.mapper.EduVisitorRecordMapper;
import com.yida.data.school.visitor.service.EduVisitorRecordService;
import com.yida.data.school.vo.visitor.ListVisitorRecordVO;
import com.yida.data.school.vo.visitor.PassCodeVO;
import com.yida.data.school.vo.visitor.StaVisitorDistributeVO;
import com.yida.data.system.feign.RemoteCommonService;
import com.yida.data.system.feign.RemoteDeptService;
import com.yida.data.user.feign.RemoteStaffService;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * 访客管理-来访人员记录表 Service实现
 *
 * @author zhanghaijun
 * @date 2021-11-09 15:04:42
 */
@Service
@RequiredArgsConstructor
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public class EduVisitorRecordServiceImpl extends ServiceImpl
    <EduVisitorRecordMapper, EduVisitorRecord> implements EduVisitorRecordService {

    private final RemoteStaffService remoteStaffService;
    private final RedisService redisService;
    private final WxPublicUtil wxPublicUtil;
    private final CommonService commonService;
    private final RemoteCommonService remoteCommonService;
    private final RemoteDeptService remoteDeptService;
    private final WxUtil wxUtil;

    @Value("${febs.pageUrl}")
    private String pageUrl;

    // 访客申请界面
    private final static String VISITOR = "/visitor/visitor/visitRequest";
    // 访客申请详情界面
    private final static String PASS_INFO = "/visitor/visitor/passInfo";
    // 保安扫描页面
    private final static String SECURITY = "/visitor/staff/security";
    //
    private final static String STAFF_RECORD = "/visitor/staff/staffRecord";

    @Override
    public IPage<ListVisitorRecordVO> listRecordPage(ListVisitorRecordDTO dto) {
        Page<ListVisitorRecordVO> page = dto.toPage();
        page.setRecords(baseMapper.listRecord(page, dto));
        for (ListVisitorRecordVO record : page.getRecords()) {
            EduStaff staff = (EduStaff) redisService
                .hget(CachePrefixConstant.STAFF_DATA, record.getStaffId().toString());
            if (staff == null) {
                staff = remoteStaffService.getStaff(record.getStaffId()).getData();
            }
            record.setStaffName(staff.getName()
                + (StrUtil.isNotBlank(staff.getPosition()) ? String.format("(%s)", staff.getPosition()) : ""));
            // 进门保安
            if (record.getInSchoolSecurityId() != null) {
                EduStaff security = (EduStaff) redisService
                    .hget(CachePrefixConstant.STAFF_DATA, record.getInSchoolSecurityId().toString());
                if (security == null) {
                    security = remoteStaffService.getStaff(record.getInSchoolSecurityId()).getData();
                }
                record.setInSchoolSecurityName(security.getName());
            }
            // 出门保安
            if (record.getOutSchoolSecurityId() != null) {
                EduStaff security = (EduStaff) redisService
                    .hget(CachePrefixConstant.STAFF_DATA, record.getOutSchoolSecurityId().toString());
                if (security == null) {
                    security = remoteStaffService.getStaff(record.getOutSchoolSecurityId()).getData();
                }
                record.setOutSchoolSecurityName(security.getName());
            }
        }
        return page;
    }

    @Override
    public void changeResult(Long id, Integer result, String reason) {
        EduVisitorRecord record = getById(id);
        Asserts.isTrue(record.getResult() == 0 || (record.getResult() == 1 && result == 3), "该申请已处理，请刷新后重试");
        record.setResult(result);
        record.setNoReason(reason);
        updateById(record);
        Integer type = null;
        String url = null;
        // 消息体
        JSONObject data = new JSONObject() {{
            set("first", new JSONObject() {{
                set("value", "访客预约审核结果通知");
                set("color", "#173177");
            }});
            set("keyword1", new JSONObject() {{
                set("value", record.getName());
                set("color", "#173177");
            }});
            set("keyword2", new JSONObject() {{
                set("value", LocalDateTimeUtil.format(record.getAppointmentTime(), "yyyy-MM-dd HH:mm"));
                set("color", "#173177");
            }});
            set("keyword3", new JSONObject() {{
                set("value", record.getReason());
                set("color", "#173177");
            }});
            set("keyword4", new JSONObject() {{
                set("value", record.getResult() == 1 ? "通过" : record.getResult() == 2 ? "拒绝" : "取消");
                set("color", "#173177");
            }});
            set("remark", new JSONObject() {{
                set("value", record.getResult() == 1 ? "请您按时来访！" : "请与被访人联系，重新申请");
                set("color", "#173177");
            }});
        }};
        switch (record.getResult()) {
            case 1:
                type = WxPublicTemplateMsgType.VISITOR_ORDER_PASS.getValue();
                url = pageUrl + PASS_INFO + "?inviteCode=" + record.getInviteCode();
                break;
            case 2:
            case 3:
                type = WxPublicTemplateMsgType.VISITOR_ORDER_REJECT.getValue();
                data.set("keyword5", new JSONObject() {{
                    set("value", record.getNoReason());
                    set("color", "#173177");
                }});
                break;
            default:
                break;
        }
        EduDeptWxPublic wxPublic = commonService.getWxPublic(record.getSchoolId());
        // 查询对应的模板消息
        EduWxPublicTemplateMsg templateMsg = (EduWxPublicTemplateMsg) redisService
            .hget(CachePrefixConstant.SYS_WXPUBLIC_TEMPLATE_MSG + wxPublic.getAppId(), type.toString());
        if (templateMsg == null) {
            templateMsg = remoteCommonService.getWxTemplateMsg(wxPublic.getAppId(), type).getData();
            redisService.hset(CachePrefixConstant.SYS_WXPUBLIC_TEMPLATE_MSG + wxPublic.getAppId(), type.toString(),
                templateMsg);
        }
        // 发送模板消息
        String accessToken = wxPublicUtil.getAccessToken(wxPublic.getAppId(), wxPublic.getSecret());
        wxPublicUtil
            .publishTemplateMsg(accessToken, record.getVisitorWxPublicOpenId(), templateMsg.getTemplateMsgId(), url, data);
    }

    @Override
    public String generateInviteCode(Long schoolId) {
        EduDeptWxPublic wxPublic = commonService.getWxPublic(schoolId);
        String accessToken = wxPublicUtil.getAccessToken(wxPublic.getAppId(), wxPublic.getSecret());
        // 生成邀请码
        String inviteCode = genertaRandomInviteCode();
        // 生成二维码
        String flag = wxPublicUtil
            .generateQR(accessToken, WxPublicQrTypeConstant.VISITOR_INVITE_CODE + "-" + inviteCode, 86400);
        redisService.set(CachePrefixConstant.SCHOOL_VISITOR_INVITE_CODE + inviteCode, 1, 86400L);
        return flag;
    }

    @Override
    public ListVisitorRecordVO commitVisitRequest(EduVisitorRecord visitorRecord) {
        // 邀请码是否有效
        Asserts.isTrue(
            redisService.hasKey(CachePrefixConstant.SCHOOL_VISITOR_INVITE_CODE + visitorRecord.getInviteCode())
                && redisService.hHasKey(CachePrefixConstant.SCHOOL_VISITOR_OPEN_ID, visitorRecord.getInviteCode())
                && count(Wrappers.<EduVisitorRecord>lambdaQuery()
                .eq(EduVisitorRecord::getInviteCode, visitorRecord.getInviteCode())) == 0, "邀请码无效，请重新申请邀请码");
        // 学校是否存在
        Asserts.isNotNull(commonService.getDept(visitorRecord.getSchoolId()), "该学校未开通访客管理");
        visitorRecord.setVisitorWxPublicOpenId(
            redisService.hget(CachePrefixConstant.SCHOOL_VISITOR_OPEN_ID, visitorRecord.getInviteCode()).toString());
        save(visitorRecord);
        redisService.del(CachePrefixConstant.SCHOOL_VISITOR_INVITE_CODE + visitorRecord.getInviteCode());
        redisService.hdel(CachePrefixConstant.SCHOOL_VISITOR_OPEN_ID, visitorRecord.getInviteCode());
        EduStaff staff = remoteStaffService.getStaff(visitorRecord.getStaffId()).getData();
        if (staff != null) {
            try {
                EduApp app = commonService
                    .getAppByCodeAndSchool(AppConstant.CONTACT_SELECT, null, visitorRecord.getSchoolId());
                BaseStaffNotice notice = new BaseStaffNotice();
                notice
                    .setText(new Text(String
                        .format("<a href='%s'>尊敬的%s老师，您有一条新的访问申请，请及时处理！</a>",
                            pageUrl + STAFF_RECORD + "?loginType=0&state=visitor_staff&userType=0&appId=" + app.getWxCorpId(),
                            staff.getName())));
                notice.setAgentid(app.getWxAgentId());
                notice.setTouser(staff.getWxId());
                wxUtil.pushStaffNotice(wxUtil.getAccessToken(app.getWxCorpId(), app.getWxSecret()), notice);
            } catch (Exception e) {
                log.error("发送访问请求失败", e);
            }
        }
        return getInfoByInviteCode(visitorRecord.getInviteCode());
    }

    @Override
    public void scanInviteCodeCall(WxPublicQr wxPublicQr) {
        // 邀请码是否已被扫描
        Object o = redisService.get(CachePrefixConstant.SCHOOL_VISITOR_INVITE_CODE + wxPublicQr.getFlag());
        if (o != null) {
            EduDeptWxPublic wxPublic = commonService.getWxPublic(wxPublicQr.getSchoolId());
            String accessToken = wxPublicUtil.getAccessToken(wxPublic.getAppId(), wxPublic.getSecret());
            StringBuilder sb = new StringBuilder();
            if (Integer.parseInt(o.toString()) == 1) {
                // 邀请码值加一  禁止再次扫描
                redisService.incr(CachePrefixConstant.SCHOOL_VISITOR_INVITE_CODE + wxPublicQr.getFlag(), 1L);
                redisService
                    .hset(CachePrefixConstant.SCHOOL_VISITOR_OPEN_ID, wxPublicQr.getFlag(),
                        wxPublicQr.getUserOpenId(), 86400L);
                // 发送消息
                sb.append("预约访问请<a href='")
                    .append(pageUrl).append(VISITOR)
                    .append("?inviteCode=").append(wxPublicQr.getFlag())
                    .append("&schoolId=").append(wxPublicQr.getSchoolId())
                    .append("'>点击此处</a>");
            } else {
                sb.append("该邀请码已被使用，请联系被访人重新申请");
            }
            wxPublicUtil.pushCustomMsgText(accessToken, wxPublicQr.getUserOpenId(), sb.toString());
        }
    }

    @Override
    public IPage<ListVisitorRecordVO> listRecordToStaff(ListVisitorRecordToStaffDTO dto) {
        return baseMapper.listRecordToStaff(dto.toPage(), dto);
    }

    @Override
    public ListVisitorRecordVO getInfoByInviteCode(String inviteCode) {
        EduVisitorRecord record = getOne(
            Wrappers.<EduVisitorRecord>lambdaQuery().eq(EduVisitorRecord::getInviteCode, inviteCode));
        Asserts.isNotNull(record, "邀请码不存在");
        String idCard = record.getIdCard();
        // 身份证号脱敏
        ListVisitorRecordVO res = new ListVisitorRecordVO();
        res.setName(record.getName());
        res.setIdCard(DesensitizedUtil.idCardNum(idCard, 3, 2));
        res.setMobile(DesensitizedUtil.mobilePhone(record.getMobile()));
        EduStaff staff = remoteStaffService.getStaff(record.getStaffId()).getData();
        res.setStaffName(staff.getName()
            + (StrUtil.isNotBlank(staff.getPosition()) ? String.format("-%s", staff.getPosition()) : ""));
        res.setStaffMobile(DesensitizedUtil.mobilePhone(staff.getMobile()));
        res.setReason(record.getReason());
        res.setReasonPic(record.getReasonPic());
        res.setAppointmentTime(record.getAppointmentTime());
        res.setId(record.getId());
        res.setStatus(record.getStatus());
        res.setResult(record.getResult());
        res.setSchoolId(record.getSchoolId());
        return res;
    }

    @Override
    public PassCodeVO getPassQr(String inviteCode) {
        ListVisitorRecordVO info = getInfoByInviteCode(inviteCode);
        PassCodeVO vo = new PassCodeVO();
        vo.setName(info.getName());
        vo.setIdCard(info.getIdCard());
        vo.setStaffName(info.getStaffName());
        vo.setStaffMobile(info.getStaffMobile());
        vo.setAppointmentTime(info.getAppointmentTime());
        if (info.getResult() != 1 || (info.getStatus() != 0 && info.getStatus() != 1)) {
            vo.setValid(1);
            return vo;
        }
        vo.setValid(0);
        EduDeptWxPublic wxPublic = commonService.getWxPublic(info.getSchoolId());
        StringBuilder sb = new StringBuilder();
        sb.append(pageUrl).append(SECURITY)
            .append("?inviteCode=").append(inviteCode)
            .append("&loginType=1&userType=0&appId=").append(wxPublic.getAppId());
        String base64;
        Dept school = commonService.getDept(info.getSchoolId());
        try {
            base64 = QrCodeUtil.generateQrCodeBase64(sb.toString(), 400, 400, school.getIcon());
        } catch (Exception e) {
            log.error("生成通行码失败", e);
            throw new FebsException("获取通行码失败");
        }
        vo.setQrBase64(base64);
        return vo;
    }

    @Override
    public void visitorInSchool(Long staffId, Long recordId, Double temp) {
        EduVisitorRecord record = getById(recordId);
        Asserts.isNotNull(record, "预约记录不存在，请与来访者者核实后重试");
        Asserts.isTrue(record.getResult() == 1, "该来访预约未通过，请与来访者者核实后重试");
        Asserts.isTrue(record.getStatus() == 0, "该访客已登记，请勿重复操作");
        record.setStatus(1);
        record.setTemp(temp);
        record.setInSchoolSecurityId(staffId);
        record.setInSchoolTime(LocalDateTime.now());
        updateById(record);
    }

    @Override
    public void visitorOutSchool(Long staffId, Long recordId) {
        EduVisitorRecord record = getById(recordId);
        Asserts.isNotNull(record, "预约记录不存在，请核实后重试");
        record.setStatus(2);
        record.setOutSchoolTime(LocalDateTime.now());
        record.setOutSchoolSecurityId(staffId);
        updateById(record);
    }

    @Override
    public Integer checkInviteCode(String inviteCode) {
        boolean used =
            count(Wrappers.<EduVisitorRecord>lambdaQuery().eq(EduVisitorRecord::getInviteCode, inviteCode)) > 0;
        int res = 1;
        if (!used) {
            if (redisService.hasKey(CachePrefixConstant.SCHOOL_VISITOR_INVITE_CODE + inviteCode)) {
                res = 0;
            } else {
                res = 2;
            }
        }
        return res;
    }

    @Override
    public void dealRequestDdl() {
        baseMapper.dealRequestDdl();
    }

    @Override
    public StaVisitorDistributeVO staVisitorDistribute(String startDate, String endDate) {
        StaVisitorDistributeVO vo = new StaVisitorDistributeVO();
        List<Dept> schools = remoteDeptService.getSchoolByArea(null, null).getData();
        if (CollUtil.isNotEmpty(schools)) {
            List<Long> schoolIds = schools.stream().map(Dept::getDeptId).collect(Collectors.toList());
            List<EduVisitorRecord> records = list(
                Wrappers.<EduVisitorRecord>lambdaQuery().in(EduVisitorRecord::getSchoolId, schoolIds)
                    .between(EduVisitorRecord::getInSchoolTime, LocalDateTimeUtil.parseDate(startDate).atStartOfDay(),
                        LocalDateTimeUtil.parseDate(endDate).atTime(LocalTime.MAX)));
            Map<String, Integer> genderMap = new HashMap<>();
            Map<Integer, Integer> timeMap = new HashMap<Integer, Integer>() {{
                for (int i = 9; i < 19; i++) {
                    put(i, 0);
                }
            }};
            // TODO: 2022/3/29 产品要求统计时间为朝九晚五
            for (EduVisitorRecord record : records) {
                // 根据身份证号判断性别
                String gender = record.getIdCard().length() == 15 ?
                    (int) record.getIdCard().charAt(14) % 2 == 0 ? "女" : "男"
                    : (int) record.getIdCard().charAt(16) % 2 == 0 ? "女" : "男";
                genderMap.put(gender, genderMap.getOrDefault(gender, 0) + 1);
                // 判断访问时间
                int hour = record.getInSchoolTime().getHour();
                if (9 <= hour && hour <= 19) {
                    timeMap.put(hour, timeMap.get(hour) + 1);
                }
            }
            vo.setGender(genderMap.entrySet().stream().map(x -> {
                NameValueVO<String, Integer> res = new NameValueVO<>();
                res.setName(x.getKey());
                res.setValue(x.getValue());
                return res;
            }).collect(Collectors.toList()));
            List<NameValueVO<Integer,Integer>> timeList = timeMap.entrySet().stream().map(x -> {
                NameValueVO<Integer, Integer> res = new NameValueVO<>();
                res.setName(x.getKey());
                res.setValue(x.getValue());
                return res;
            }).sorted(Comparator.comparing(NameValueVO::getName)).collect(Collectors.toList());
            vo.setTime(timeList);
            vo.setNum(records.size());
        }
        return vo;
    }

    private String genertaRandomInviteCode() {
        return LocalDateTimeUtil.toEpochMilli(LocalDateTime.now()) +
            RandomUtil.randomString(RandomUtil.BASE_CHAR_NUMBER, 4);
    }
}
