package com.malk.hake.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.malk.hake.service.HKClient;
import com.malk.server.aliwork.YDConf;
import com.malk.server.aliwork.YDParam;
import com.malk.server.common.McException;
import com.malk.server.dingtalk.DDConf;
import com.malk.service.aliwork.YDClient;
import com.malk.service.aliwork.YDService;
import com.malk.service.dingtalk.DDClient;
import com.malk.service.dingtalk.DDClient_Attendance;
import com.malk.service.dingtalk.DDClient_Contacts;
import com.malk.service.dingtalk.DDService;
import com.malk.utils.*;
import lombok.SneakyThrows;
import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;

@Service
@Slf4j
public class HKImplClient implements HKClient {

    @Autowired
    private YDClient ydClient;

    // monitor接口ip
    String _getEnvApi(String path) {
        if (UtilEnv.getActiveProfile().equals(UtilEnv.ENV_PROD)) {
            return "http://172.16.20.13:10002/api/public" + path;
        }
        return "http://116.228.113.106:10002/api/public" + path;
//        return "http://116.228.113.106:10001/api/public" + path;
    }

    // monitor 考勤同步：001_1.1 测试环境；001.1 生产环境
    String _getMonitorApi(String path) {
        if (UtilEnv.getActiveProfile().equals(UtilEnv.ENV_PROD)) {
            return "https://172.16.20.13:8001/zh/001.1" + path;
        }
        return "https://116.228.113.106:10028/zh/001.1" + path;
    }

    private Map MATE;

    private Map _getMeta() {
        if (ObjectUtil.isNull(MATE)) {
            MATE = (Map) UtilFile.readJsonObjectFromResource("static/json/form.json"); // 本地匹配宜搭组件ID
        }
        return MATE;
    }

    /**
     * 创建/更新数据
     */
    private String upsertData(String code, Map data) {
        // 组织单据数据
        Map meta = _getMeta();
        Map<String, ?> component = (Map) ((Map) meta.get(code)).get("compIds");
        Map formData = UtilMap.empty();
        for (String key : component.keySet()) {
            if (key.startsWith("employeeField_")) {
                // getString, 避免 [null] 数据类型
                formData.put(key, Arrays.asList(UtilMap.getString(data, (String) component.get(key))));
            } else if (key.startsWith("tableField_")) {
                List<Map> rows = (List<Map>) data.get("rows");
                List<Map> details = new ArrayList<>();
                Map<String, String> compIds = (Map) component.get(key);
                for (Map row : rows) {
                    Map detail = UtilMap.empty();
                    for (String prop : compIds.keySet()) {
                        detail.put(prop, row.get(compIds.get(prop)));
                    }
                    details.add(detail);
                }
                formData.put(key, details);
            } else if (key.startsWith("dateField_")) {
                String date = String.valueOf(data.get(component.get(key)));
                if (StringUtils.isNotBlank(date)) {
                    formData.put(key, UtilDateTime.parse(date, "yyyy/MM/dd").getTime());
                }
            } else {
                Object value = data.get(component.get(key));
                formData.put(key, value);
            }
        }
        // 发起人匹配规则
        String userId = _getUserId(code, meta, data);
        YDConf.FORM_OPERATION operation = YDConf.FORM_OPERATION.start;
        String processCode = String.valueOf(((Map) meta.get(code)).get("processCode"));
        if (UtilString.isBlankCompatNull(processCode)) {
            operation = YDConf.FORM_OPERATION.create;
        }
        String formInstanceId = UtilMap.getString(data, "instanceId");
        YDParam ydParam = YDParam.builder()
                .formUuid(String.valueOf(((Map) meta.get(code)).get("formUuid")))
                .processCode(processCode)
                .userId(userId)
                .formDataJson(JSON.toJSONString(formData))
                .updateFormDataJson(JSON.toJSONString(formData))
                .useLatestVersion(true)
                .build();
        // 匹配更新机制: Upsert操作
        if (StringUtils.isNotBlank(formInstanceId)) {
            ydParam.setFormInstanceId(formInstanceId);
            operation = YDConf.FORM_OPERATION.update;
        }
        Object result = ydClient.operateData(ydParam, operation);
        return StringUtils.isNotBlank(formInstanceId) ? formInstanceId : (String) result;
    }

    /// 发起人处理
    String _getUserId(String code, Map meta, Map data) {
        String userId = String.valueOf(data.get(String.valueOf(((Map) meta.get(code)).get("creator"))));
        // 报价单
        if ("BJ".equals(code)) {
            if (Arrays.asList("样品", "大货").contains(data.get("quoteType"))) {
                userId = String.valueOf(data.get("dtOurReferencePersonId"));
            } else {
                userId = String.valueOf(data.get("dtSellerPersonId"));
            }
        }
        if (UtilString.isBlankCompatNull(userId)) {
            userId = YDConf.PUB_ACCOUNT;
        }
        return userId;
    }

    /**
     * 通用流程发起
     */
    @SneakyThrows
    @Override
    public Map<String, Object> startProcess(String code, Map data) {
        String instanceId = upsertData(code, data);
        Map meta = _getMeta();
        // 回传钉钉单据号
        Thread.sleep(800);
        Map form = ydClient.queryData(YDParam.builder()
                .formInstanceId(String.valueOf(instanceId))
                .build(), YDConf.FORM_QUERY.retrieve_id).getFormData();
        String url = "https://pxi03f.aliwork.com/APP_QWUVLI1R6XYUXWAOPF6O/processDetail?procInsId=" + instanceId;
        return UtilMap.map("ddProcessId, dtNumber, dtUrl", instanceId, form.get(meta.get(code + "_RC")), url);
    }

    @Autowired
    private DDClient ddClient;

    @Autowired
    private DDClient_Contacts ddClient_contacts;

    @Autowired
    private DDService ddService;

    private static final String AUTH = "bbb76928-a9db-42fa-ac83-cc9a2ed75162";

    /**
     * 通用审批回调 [区分类型, 宜搭提示不同报错信息]
     */
    @Override
    public void callbackProcess(Map data) {

        // 拒绝审批意见必填校验
        McException.assertParamException_Null(data, "Result");
        // prd 4.17 取消拒绝校验, 统一返回 [审核人xxx拒绝], monitor通过活动跳转
        //if ("0".equals(data.get("Result"))) {
        // McException.assertParamException_Null(data, "Remark");
        //}
        // 回调monitor审批信息
        data.put("Url", "https://pxi03f.aliwork.com/APP_QWUVLI1R6XYUXWAOPF6O/processDetail?procInsId=" + data.get("DdProcessId"));
        data.put("ApprovalDate", UtilDateTime.formatLocalDateTime(LocalDateTime.now()));
        Map UserInfo = ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), String.valueOf(data.get("Approver")));
        // prd 4.17 取消拒绝校验, 统一返回 [审核人xxx拒绝], monitor通过活动跳转
        if ("0".equals(data.get("Result")) && !data.containsKey("Remark")) {
            data.put("Remark", "审批人 【" + UserInfo.get("name") + "】拒绝");
        }
        data.put("Approver", String.valueOf(UserInfo.get("name")));
        log.info("审批回调, {}", JSON.toJSONString(data));
        // prd: 通过 http status 判定, 200 即为成功
        String rsp = UtilHttp.doPost(_getEnvApi("/Approval"), UtilMap.map("Authorization", AUTH), UtilMap.map("Code", data.get("Code")), data);
        Map result = (Map) JSON.parse(rsp);
        McException.assertException(result.containsKey("errorMsg"), String.valueOf(result.get("status")), String.valueOf(result.get("errorMsg")), "Monitor");
    }

    /**
     * 通讯录同步 [考勤同步前获取人员信息]
     */
    @Override
    @Synchronized
    public List<String> syncContact(boolean isAttendance) {

        List<String> uIds = new ArrayList<>();
        List<Long> deptList;

        // prd: 非全量同步: 总部\哈克\负责人
        if (!isAttendance) {
            deptList = UtilList.asList(DDConf.TOP_DEPARTMENT);
            deptList.addAll(ddClient_contacts.getDepartmentId_all(ddClient.getAccessToken(), true, 129113277L)); // 总部
            deptList.addAll(ddClient_contacts.getDepartmentId_all(ddClient.getAccessToken(), true, 128984162L)); // 哈克
        } else {
            // prd 0529 上海考勤同步, 只需要生产车间人员
            deptList = ddClient_contacts.getDepartmentId_all(ddClient.getAccessToken(), true, 369113480L);
        }
        for (long deptId : deptList) {
            List<String> userIds = ddClient_contacts.listDepartmentUserId(ddClient.getAccessToken(), deptId);
            if (userIds.size() == 0) {
                continue;
            }
            // prd: 取第3层作为部门, 为空取值总经办
            String dpetCascade = ddService.getUserDepartmentHierarchyJoin(ddClient.getAccessToken(), userIds.get(0), "-");
            String deptName = "总经办";
            String[] depts = dpetCascade.split("-");
            if (depts.length >= 3) {
                deptName = depts[2];
            }
            if (isAttendance) {
                uIds.addAll(userIds);
                continue;
            }
            for (String userId : userIds) {
                Map userInfo = ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), userId);
                Map data = UtilMap.map("employeeNumber, employeeName, dtPersonId, lastName", userInfo.get("job_number"), userInfo.get("name"), userInfo.get("userid"), userInfo.get("title"));
                // prd: 手机号花名册未展示, 因此添加判空, 避免覆盖Monitor
                if (StringUtils.isNotBlank(String.valueOf(userInfo.get("mobile")))) {
                    data.put("mobilePhone", userInfo.get("mobile"));
                }
                if (userInfo.containsKey("org_email")) {
                    data.put("email", userInfo.get("org_email"));
                }
                if (userInfo.containsKey("hired_date")) {
                    data.put("employeeStartDate", UtilDateTime.formatDate(new Date(UtilMap.getLong(userInfo, "hired_date"))));
                }
                data.put("employeeDept", deptName);
                data.put("dtDepts", dpetCascade);
                data.put("dtPersonGroup", "");
                log.info("同步人员, {}", JSON.toJSONString(UtilMap.map("employees", Arrays.asList(data))));
                _syncContact(data, userId);
            }
        }

        if (isAttendance) {
            return uIds;
        }

        // 离职人员同步
        Date sDate = UtilDateTime.convertToDateFromLocalDateTime(LocalDateTime.now().minusDays(1));
        List<Map<String, String>> maps = ddClient_contacts.getLeaveEmployeeRecords(ddClient.getAccessToken(), sDate, null);
        for (Map<String, String> map : maps) {
            // prd 钉钉离职数据获取不到工号, 通过99999 monitor判断, 若存在则更新部门, 负责忽略
            Map data = UtilMap.map("employeeNumber, employeeName, dtPersonId, mobilePhone, employeeDept", "99999", map.get("name"), map.get("userId"), map.get("mobile"), "离职");
            data.put("employeeFinishDate", map.get("leaveTime").split("T")[0]);
            _syncContact(data, map.get("userId"));
            log.info("离职人员, {}", JSON.toJSONString(UtilMap.map("employees", Arrays.asList(data))));
        }

        userIdReflect = null;
        log.info("同步完成::重新同步人员映射表", getUserIdReflect().size());
        return uIds;
    }

    /// 员工信息写入monitor
    void _syncContact(Map data, String userId) {

        // prd: 通过 http status 判定, 200 即为成功
        String rsp = UtilHttp.doPost(_getEnvApi("/EmployeeImport"), UtilMap.map("Authorization", AUTH), null, UtilMap.map("employees", Arrays.asList(data)));
        Map result = (Map) JSON.parse(rsp);

        // 12.17 记录 monitor userId, 用于传递考勤同步数据
        List<String> employIds = new ArrayList<String>();
        if (UtilList.isNotEmpty(UtilMap.getList(result, "newPerson"))) {
            employIds = UtilMap.getList(result, "newPerson");
        }
        if (UtilList.isNotEmpty(UtilMap.getList(result, "fail"))) {
            result = (Map) UtilMap.getList(result, "fail").get(0); // 单条同步, 记录错误人员
        }
        //McException.assertException(result.containsKey("msg"), String.valueOf(result.get("status")), String.valueOf(result.get("msg")), "Monitor");
        if (result.containsKey("msg")) {
            ydClient.operateData(YDParam.builder()
                    .formUuid("FORM-W2A66Z91912F39B2AHY0VD4CRK9F3C4JB74OLN")
                    .formDataJson(JSON.toJSONString(UtilMap.map("dateField_lo47byyd, employeeField_lo47byyj, textareaField_lo47byyo, textareaField_lo47sanj", new Date().getTime(), userId, result, data)))
                    .build(), YDConf.FORM_OPERATION.create);
        } else {
            // prd 新增用户会返回 newPerson 集合, 更新不会 (若包含了中横线, 则格式为: 工号-EmployeeId, 即 monitorId)
            if (!employIds.isEmpty()) {
                String monitorId = employIds.get(0);
                if (monitorId.contains("-")) {
                    monitorId = monitorId.split("-")[1];
                }
                List<Map> searchCondition = Arrays.asList(YDConf.searchCondition_TextFiled("textField_m4sjp8pf", userId, "eq"));
                ydClient.operateData(YDParam.builder()
                        .formUuid("FORM-96C8BF4B18044DF783ADFE1CF7931FE9P4BW")
                        .searchCondition(JSON.toJSONString(searchCondition))
                        .formDataJson(JSON.toJSONString(UtilMap.map("employeeField_lo47byyj, textField_m4sjp8pf, textField_m4sjp8pg", Arrays.asList(userId), userId, monitorId)))
                        .build(), YDConf.FORM_OPERATION.upsert_v2);
            }
        }
    }

    @Autowired
    private YDService ydService;

    /**
     * 付款数据同步
     */
    @Override
    public void syncMonitor(String code, Map data) {

        McException.assertAccessException(!Arrays.asList("FKSQ", "YFKSQ").contains(code), "单据编码未匹配");
        boolean isYF = "YFKSQ".equals(code);

        // prd 25.01.06 预付款订单数据支持筛选: 采购订单号、供应商号、供应商名称的筛选条件，如果为空就查询所有的数据，有值就查询出对应值的数据
        Map body = UtilMap.empty();
        if (isYF) {
            UtilMap.putAll(body, data);
        }
        String rsp = UtilHttp.doPost(_getEnvApi(isYF ? "/PrePayment" : "/Payment"), UtilMap.map("Authorization", AUTH), null, body);
        Map result = (Map) JSON.parse(rsp);
        List<Map> dataList = (List<Map>) UtilMap.getList(result, isYF ? "orders" : "rows");
        log.info("付款数据同步, {} {}", code, dataList.size());

        // prd: 通过 http status 判定, 200 即为成功
        for (Map row : dataList) {
            Map search = null;
            if (isYF) {
                search = YDConf.searchCondition_TextFiled("textField_lnmyiqan", UtilMap.getString(row, "orderNumber"), "eq");
            } else {
                search = YDConf.searchCondition_TextFiled("textField_lnmzbyzu", UtilMap.getString(row, "invoiceNumber"), "eq");
            }
            // 匹配是否新增
            List<Map> formList = ydService.queryFormData(isYF ? "FORM-FCD09241E15D4E53A901D54CA3FBB084PQ6P" : "FORM-4E69EBA1416047078D14601AF17A05643350", JSON.toJSONString(Arrays.asList(search)));
            if (formList.size() == 0) {
                // 宜搭付款控制字段
                row.put("status", "否");       // 反馈中
                row.put("payed", 0);           // 已付款
            } else {
                Map record = formList.get(0);
                row.put("status", record.get("radioField_ls3ddkie"));       // 反馈中
                row.put("payed", record.get("numberField_ls3ddkif"));       // 已付款
                row.put("instanceId", record.get("instanceId"));
            }
            upsertData(code, row);
        }
    }

    // ppExt: 基于 OkHttp 库实现, https带ip请求兼容性, 忽略证书校验 OkHttpUtil 类 [ 使用 HttpUtil 报异常 ]
    @SneakyThrows
    private Map okRequest(String url, String sessionId, Map data) {
        log.debug("ok request, {}, {}", url, data);
        OkHttpClient client = new OkHttpClient().newBuilder()
                .sslSocketFactory(OkHttpUtil.getIgnoreInitedSslContext().getSocketFactory(), OkHttpUtil.IGNORE_SSL_TRUST_MANAGER_X509)
                .hostnameVerifier(OkHttpUtil.getIgnoreSslHostnameVerifier())
                .build();
        MediaType mediaType = MediaType.parse("application/json");
        RequestBody body = RequestBody.create(mediaType, JSON.toJSONString(data));
        Request request = new Request.Builder()
                .url(url)
                .addHeader("X-Monitor-SessionId", sessionId)
                .method("POST", body)
                .build();

        Response response = client.newCall(request).execute();
        String rsp = response.body().string();
        log.debug("ok response, {}", rsp);
        try {
            // prd 25.06.25 monitor接口升级, 考勤认证取值从body移到header
            Map result = JSON.parseObject(rsp);
            result.put("SessionId", response.headers().get("X-Monitor-SessionId"));
            return result;
        } catch (Exception e) {
            McException.exceptionAccess(rsp);
        }
        return null;
    }

    // 调用monitor标准接口, 需要获取sessionId
    private String _getAuthSessionId() {

        String url = _getMonitorApi("/login");
        Map auth = UtilMap.map("Username, Password, ForceRelogin", "API.DD", "DD123456", true);
        Map result = okRequest(url, "", auth);
        return UtilMap.getString(result, "SessionId");
    }

    // 清理标记位
    @Override
    public void clearClockStatus() {
        this.checkStatus = new HashMap<>();
    }

    // 考勤同步
    private void _syncCheckingRecord(boolean isIn, String time, String employeeId) {
        String type = isIn ? "ClockIn" : "ClockOut";
        String url = _getMonitorApi("/api/v1/TimeRecording/RecordingDays/" + type);
        // 若不传递 TimeStamp, 则会以当前时间
        if (StringUtils.isBlank(time)) {
            time = UtilDateTime.formatDateTime(new Date());
        }
        Map param = UtilMap.map("EmployeeId, TimeStamp", employeeId, time);
        okRequest(url, _getAuthSessionId(), param);
    }

    // monitor 用户ID 与钉钉 userId 映射表 (通讯录同步后重置)
    private List<Map> userIdReflect;

    public List<Map> getUserIdReflect() {
        if (ObjectUtil.isNull(userIdReflect)) {
            userIdReflect = ydService.queryFormData_all(YDParam.builder()
                    .formUuid("FORM-96C8BF4B18044DF783ADFE1CF7931FE9P4BW")
                    .build());
        }
        return userIdReflect;
    }

    // 因 monitor 写入要求, 且钉钉打卡回调没有返回状态, 只有时间和人员, 添加写入标识
    private Map<String, Boolean> checkStatus = new HashMap();

    /**
     * prd monitor clockIn, clockOut 必须成对出现, 且后一次时间必须在前一次时间之后, 否则均同步异常; ppExt: prd 25-05-06 考勤方案修正为全量, 确保上下班正常
     * [方案: in/out 若是同一个时间可以反复调用]
     * 1. 钉钉已设置为严格打卡模式, 一次上班卡\一次下班卡, 成对出现, 不取值最早和最晚时间
     * 2. 每天早上7点重置打卡标记状态, 避免通过标记第一次打卡就是 clock-out 情况, 导致后续与实际考勤一直异常
     * 3. 首次 clock-in 若需要clock-out, 则先调用 clock-out 注意传递的时间为前一日 23:59:58, 然后再clock-in打卡时间; 反之传递当天 00:59:58. 以修正状态
     */

    // 钉钉打卡回调
    @Override
    public void callbackAttendance(Map data) {

        String userId = UtilMap.getString(data, "userId");
        String timeStamp = UtilDateTime.formatDateTime(new Date(UtilMap.getLong(data, "checkTime")));

        boolean isIn = true;
        if (checkStatus.containsKey(userId) && !checkStatus.get(userId)) {
            isIn = false;
        }
        Optional optional = this.getUserIdReflect().stream().filter(item -> userId.equals(UtilMap.getString(item, "textField_m4sjp8pf"))).findAny();
        if (!optional.isPresent()) {
            McException.exceptionAccess("考勤同步, 用户未匹配");
        }
        String employeeId = UtilMap.getString((Map) optional.get(), "textField_m4sjp8pg");
        try {
            _syncCheckingRecord(isIn, timeStamp, employeeId);
        } catch (Exception e) {
            // fixme:  因 monitor 写入要求, 重复下班 expected Out got In 报错; 重复上班 expected In got Out 报错
            if (e.getMessage().contains("expected In got Out") || e.getMessage().contains("expected Out got In")) {
                String tTime = UtilDateTime.formatDate(new Date(UtilMap.getLong(data, "checkTime") - 24 * 60 * 60 * 1000));
                if (e.getMessage().contains("expected In got Out")) {
                    tTime = tTime + " 23:59:58";
                }
                if (e.getMessage().contains("expected Out got In")) {
                    tTime = tTime + " 00:59:58";
                }
                _syncCheckingRecord(!isIn, tTime, employeeId);
                _syncCheckingRecord(isIn, timeStamp, employeeId);
            } else {
                log.error("打卡异常, {}", e.getMessage());
            }
        }
    }

    @Autowired
    private DDClient_Attendance ddClient_attendance;

    /**
     * 全量同步考勤
     * ppExt: 同步说明
     * - monitor 考勤数据同步需要成对传递, 次日考勤不会更新, 若上班\下班缺卡, 则会导致后续都会持续异常. 且考勤记录不能早于之前的时间
     * - 若是存在缺卡情况, 缺上班卡则下班卡减1s作为上班时间, 缺下班卡则上班卡加1s作为下班时间; 确保考勤先从clockIn 开始, clockOut 结束;
     * - 同步前一日考勤全量数据; 取消钉钉考勤回调, 补卡同步, 不能成对出现的场景. 记录异常数据到宜搭日志, 后续手动修正
     */
    @Override
    public void syncAttendance(String tDate, List<String> userIds) {

        if (UtilString.isBlankCompatNull(tDate)) {
            tDate = UtilDateTime.formatDate(new Date());
        }
        if (ObjectUtil.isNull(userIds)) {
            userIds = syncContact(true);
        }
        List<Map> reflects = getUserIdReflect(); // 钉钉Id -> monitorId
        log.info("考勤同步信息, 人员数量 = {}, id映射表数据 = {}", userIds.size(), reflects.size());

        for (String userId : userIds) {
            Optional optional = reflects.stream().filter(item -> userId.equals(UtilMap.getString(item, "textField_m4sjp8pf"))).findAny();
            if (!optional.isPresent()) {
                Map formData = UtilMap.map("dateField_lo47byyd, employeeField_lo47byyj, textareaField_lo47byyo, textareaField_lo47sanj", new Date().getTime(), userId, "monitor id 未匹配, 请管理员维护", "");
                _attendanceExceptionRecord(formData);
                continue;
            }
            String employeeId = UtilMap.getString((Map) optional.get(), "textField_m4sjp8pg");
            if (employeeId.contains("_")) {
                employeeId = employeeId.split("_")[1];
            }
            Map record = ddClient_attendance.getAttendanceRecord(ddClient.getAccessToken(), userId, tDate);
            List<Map> checks = UtilMap.getList(record, "attendance_result_list");
            // 缺卡处理逻辑
            if (checks.size() == 1) {
                Map tmp = UtilMap.empty();
                long tms = UtilDateTime.parseDateTime(UtilMap.getString(checks.get(0), "user_check_time")).getTime();
                if ("OffDuty".equals(UtilMap.getString(checks.get(0), "check_type"))) {
                    tmp.put("check_type", "OnDuty");
                    tmp.put("user_check_time", UtilDateTime.formatDateTime(new Date(tms - 1000L)));
                } else {
                    tmp.put("check_type", "OffDuty");
                    tmp.put("user_check_time", UtilDateTime.formatDateTime(new Date(tms + 1000L)));
                }
                checks.add(tmp);
            }
            checks.sort(Comparator.comparingLong(item -> UtilDateTime.parseDateTime(UtilMap.getString(item, "user_check_time")).getTime())); // 排序
            log.info("考勤记录, {}, result = {}, record = {}", UtilMap.getString((Map) optional.get(), "employeeField_lo47byyj"), checks.size(), UtilMap.getList(record, "check_record_list").size());

            for (Map check : checks) {
                boolean isIn = !"OffDuty".equals(UtilMap.getString(check, "check_type"));
                try {
                    _syncCheckingRecord(isIn, UtilMap.getString(check, "user_check_time"), employeeId);
                } catch (McException e) {
                    Map formData = UtilMap.map("dateField_lo47byyd, employeeField_lo47byyj, textareaField_lo47byyo, textField_m9r144bh", new Date().getTime(), userId, e.getMessage(), employeeId);
                    formData.put("radioField_m9r144bo", isIn ? "上班" : "下班");
                    formData.put("textField_m9r144br", UtilMap.getString(check, "user_check_time"));
                    formData.put("textField_m9r144bs", UtilMap.getString(check, "plan_check_time"));
                    _attendanceExceptionRecord(formData);
                    log.error("同步 monitor 异常, {}", e.getMessage());
                }
            }
        }
    }

    // 写入考勤异常记录
    private void _attendanceExceptionRecord(Map formData) {
        ydClient.operateData(YDParam.builder()
                .formUuid("FORM-E6219F15B3BE418382378DAC42CCEC49PNZQ")
                .formDataJson(JSON.toJSONString(formData))
                .build(), YDConf.FORM_OPERATION.create);
    }

    /**
     * prd 数据修正 2025-05-06
     * - fixme: 存在钉钉考勤回调历史原因, 若已有当前时间打卡, 则之前时间数据无法写入. 更新当前生产环境所有人考勤下班时间为 17:00
     * - ppExt: 若 报错为 Recording template is not valid: FutureRecordingsDetected, 则存在有记录已大于当前传递考勤时间
     */
    @Override
    public void _correctionAttendance() {

        List<String> userIds = syncContact(true);
        List<Map> reflects = getUserIdReflect(); // 钉钉Id -> monitorId
        log.info("考勤同步信息, 人员数量 = {}, id映射表数据 = {}", userIds.size(), reflects.size());

        for (String userId : userIds) {
            Optional optional = reflects.stream().filter(item -> userId.equals(UtilMap.getString(item, "textField_m4sjp8pf"))).findAny();
            if (!optional.isPresent()) {
                Map formData = UtilMap.map("dateField_lo47byyd, employeeField_lo47byyj, textareaField_lo47byyo, textareaField_lo47sanj", new Date().getTime(), userId, "monitor id 未匹配, 请管理员维护", "");
                _attendanceExceptionRecord(formData);
                continue;
            }
            String employeeId = UtilMap.getString((Map) optional.get(), "textField_m4sjp8pg");
            if (employeeId.contains("_")) {
                employeeId = employeeId.split("_")[1];
            }
            try {
                _syncCheckingRecord(false, "2025-05-06 17:00:00", employeeId);
            } catch (McException e) {
                if (!(e.getMessage().contains("expected In got Out") || e.getMessage().contains("expected Out got In"))) {
                    log.error(UtilMap.getString((Map) optional.get(), "employeeField_lo47byyj"), e.getMessage(), e);
                }
            }
        }
    }

    @Override
    public void test() {

//        String sId = _getAuthSessionId();
//        _syncCheckingRecord(true, "2025-05-07 09:04:45", "1082855734708073203");
//        log.info("xxx, {}", sId);
    }
}


