package jnpf.attendance.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.xuyanwu.spring.file.storage.FileInfo;
import cn.xuyanwu.spring.file.storage.MockMultipartFile;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import jnpf.SocketApi;
import jnpf.attendance.annotation.Machine;
import jnpf.attendance.mapper.AttendanceMachineManageMapper;
import jnpf.attendance.mapper.AttendanceUserFaceMapper;
import jnpf.attendance.mapper.AttendanceUserFingerprintMapper;
import jnpf.attendance.service.AttenceMachineService;
import jnpf.attendance.service.AttendanceClockInService;
import jnpf.attendance.service.MachineStrategy;
import jnpf.base.ActionResult;
import jnpf.base.UserInfo;
import jnpf.base.service.SuperServiceImpl;
import jnpf.constant.FileTypeConstant;
import jnpf.entity.attendance.AttendanceMachineManage;
import jnpf.entity.attendance.AttendanceUserFace;
import jnpf.entity.attendance.AttendanceUserFingerprint;
import jnpf.entity.attendance.FtbAttendanceClockIn;
import jnpf.enums.attendance.ActionEnum;
import jnpf.enums.attendance.AttendanceTypeEnum;
import jnpf.enums.attendance.ClockInStatusEnum;
import jnpf.enums.attendance.MachineEnum;
import jnpf.file.FileApi;
import jnpf.file.FileUploadApi;
import jnpf.model.attendance.dto.ClockInDto;
import jnpf.model.attendance.vo.AttendanceRuleVo;
import jnpf.model.attendance.vo.GroupInfoVo;
import jnpf.model.attendance.vo.attendance.UserTenantVo;
import jnpf.permission.UserApi;
import jnpf.permission.V2UserApi;
import jnpf.permission.model.user.PartUserInfoVo;
import jnpf.permission.model.user.UserNoInfoVo;
import jnpf.permission.vo.v2.user.UserBoundVO;
import jnpf.util.*;
import jnpf.util.attendance.MachineStrategyFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 考勤机服务实现
 *
 * @author yanwenfu
 * @create 2023-11-29
 */
@Slf4j
@Service
public class AttenceMachineServiceImpl extends SuperServiceImpl<AttendanceMachineManageMapper, AttendanceMachineManage> implements AttenceMachineService {

    @Autowired
    private V2UserApi v2UserApi;

    @Autowired
    private UserApi userApi;

    @Autowired
    private AttendanceUserFaceMapper attendanceUserFaceMapper;

    @Resource
    private AttendanceUserFingerprintMapper attendanceUserFingerprintMapper;

    @Autowired
    private AttendanceClockInService attendanceClockInService;

    @Autowired
    private AttendanceMachineManageMapper attendanceMachineManageMapper;

    @Autowired
    private CustomTenantUtil customTenantUtil;

    @Autowired
    private UserProvider userProvider;

    @Autowired
    private FileApi fileApi;

    @Autowired
    private FileUploadApi fileUploadApi;

    @Autowired
    private SocketApi socketApi;

    @Resource
    private MachineStrategyFactory machineStrategyFactory;

    @Override
    public void sendUserToMachine(String code, String userId, String sn) {

        UserInfo userInfo = userProvider.get();
        // 查询考勤组用户
        ActionResult<List<UserBoundVO>> userList = v2UserApi.getAllUserInfoBatch(Stream.of(userId).collect(Collectors.toList()), null);
        log.error("查询用户信息：{}, 参数：{}， {}， {}", userList, code, userId, sn);
        if (200 != userList.getCode() || CollectionUtil.isEmpty(userList.getData())) {
            return;
        }
        UserBoundVO userBoundVO = userList.getData().get(0);
        PartUserInfoVo user = BeanUtil.copyProperties(userBoundVO, PartUserInfoVo.class);
        user.setRealName(userBoundVO.getUserName());
        user.setUserId(userBoundVO.getId());
        MachineEnum machineEnum = MachineEnum.getMachineEnum(code);
        if (null == machineEnum) {
            log.error("未找到考勤设备");
            return;
        }
        MachineStrategy machineStrategy = machineStrategyFactory.getMachineStrategy(machineEnum);
        machineStrategy.addUserToMachine(userInfo, user, sn);
    }

    @Override
    public void sendUserToMachine(String code, PartUserInfoVo user, String sn) {

        UserInfo userInfo = userProvider.get();
        MachineEnum machineEnum = MachineEnum.getMachineEnum(code);
        if (null == machineEnum) {
            log.error("未找到考勤设备");
            return;
        }
        MachineStrategy machineStrategy = machineStrategyFactory.getMachineStrategy(machineEnum);
        machineStrategy.addUserToMachine(userInfo, user, sn);
    }

    @Override
    public void updateUserInfoByWebsocket(String sn, String userId, String userName) {

        UserInfo userInfo = userProvider.get();
        try {
            JSONObject json = new JSONObject();
            json.set("cmd", "to_device");
            json.set("from", "server");
            json.set("to", sn);
            JSONObject json1 = new JSONObject();
            json1.set("cmd", "editUser");
            json1.set("user_id", userInfo.getTenantId() + "@" + userId);
            json1.set("name", userName);
            json.set("data", json1);
            socketApi.sendMsg2Client(MachineEnum.KAI_JIA_YI.getValue(), sn, json.toString());
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }

    @Override
    public void deleteUser(String sn, String userId) {

        UserInfo userInfo = userProvider.get();
        try {
            JSONObject json = new JSONObject();
            json.set("cmd", "to_device");
            json.set("from", "server");
            json.set("to", sn);
            JSONObject json1 = new JSONObject();
            json1.set("cmd", "delUser");
            json1.set("user_id", userInfo.getTenantId() + "@" + userId);
            json.set("data", json1);
            socketApi.sendMsg2Client(MachineEnum.KAI_JIA_YI.getValue(), sn, json.toString());
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }

    @Override
    public void deleteUserList(String code, String sn, List<String> userIds) {

        UserInfo userInfo = userProvider.get();

        MachineEnum machineEnum = MachineEnum.getMachineEnum(code);
        if (null == machineEnum) {
            log.error("未找到考勤设备");
            return;
        }
        MachineStrategy machineStrategy = machineStrategyFactory.getMachineStrategy(machineEnum);
        machineStrategy.deleteUserList(userInfo, userIds, sn);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Map<String, Object> updateUserInfoPhoto(Map<String, Object> params) {

        Map<String, Object> map = new HashMap<>();
        // 查询数据库是否存在用户数据, 存在, 更新, 不存在, 新增
        Object content = params.get("content");
        JSONArray jsonArray = new JSONArray(content);
        List<JSONObject> list = jsonArray.toList(JSONObject.class);
        String user = list.get(0).get("user_id").toString();
        String[] split = user.split("@");
        if (split.length == 2) {
            customTenantUtil.checkOutTenant(split[0]);
        } else {
            map.put("Result", -2);
            map.put("Msg", "切换租户失败");
            return map;
        }
        for (JSONObject json : list) {
            String userId = json.get("user_id").toString().split("@")[1];
            Object fp = json.get("fp");
            if (null != fp) {
                log.error("fp: {}", fp);
                // 删除原有的指纹
                LambdaUpdateWrapper<AttendanceUserFingerprint> fingerprintUpdateWrapper = new LambdaUpdateWrapper<AttendanceUserFingerprint>()
                        .set(AttendanceUserFingerprint::getDeleteMark, ConstantUtil.NUM_FALSE)
                        .set(AttendanceUserFingerprint::getDeleteTime, new Date())
                        .eq(AttendanceUserFingerprint::getUserId, userId)
                        .eq(AttendanceUserFingerprint::getDeleteMark, ConstantUtil.NUM_TRUE);
                attendanceUserFingerprintMapper.update(null, fingerprintUpdateWrapper);
                // 新增新的指纹
                JSONArray insertFpArray = JSONUtil.parseArray(fp);
                List<AttendanceUserFingerprint> insertFpList = new ArrayList<>();
                insertFpArray.toList(JSONObject.class).forEach(v -> {
                    AttendanceUserFingerprint fingerprint = new AttendanceUserFingerprint();
                    fingerprint.setId(FtbUtil.getId());
                    fingerprint.setUserId(userId);
                    fingerprint.setDataId(v.get("id").toString());
                    fingerprint.setData(v.get("data").toString());
                    fingerprint.setDataName(v.get("name").toString());
                    insertFpList.add(fingerprint);
                });
                if (!insertFpList.isEmpty()) {
                    insertFpList.forEach(v -> attendanceUserFingerprintMapper.insert(v));
                }
            }
            Object vlPhoto = json.get("vl_photo");
            if (null != vlPhoto) {
                String url = changeBase64ToImage(vlPhoto.toString());
                log.error("图片路径: {}", url);
                updateUserPhoto(url, userId);
            }
        }
        map.put("Result", 0);
        map.put("Msg", "更新成功");
        return map;
    }

    /**
     * 更新考勤机回传的人脸图片
     * @param url 人脸图片
     * @param userId 用户id
     */
    private void updateUserPhoto(String url, String userId) {

        LambdaQueryWrapper<AttendanceUserFace> userFaceQueryWrapper = new LambdaQueryWrapper<AttendanceUserFace>()
                .eq(AttendanceUserFace::getUserId, userId)
                .eq(AttendanceUserFace::getDeleteMark, ConstantUtil.NUM_FALSE);
        AttendanceUserFace userFace = attendanceUserFaceMapper.selectOne(userFaceQueryWrapper);
        if (null != userFace) {
            // 更新人脸数据
            LambdaUpdateWrapper<AttendanceUserFace> userFaceUpdateWrapper = new LambdaUpdateWrapper<AttendanceUserFace>()
                    .eq(AttendanceUserFace::getUserId, userId);
            userFace.setFaceData(url);
            userFace.setFaceDataThumbnail(url);
            attendanceUserFaceMapper.update(userFace, userFaceUpdateWrapper);
        } else {
            // 新增人脸数据
            AttendanceUserFace insertUserFace = new AttendanceUserFace();
            insertUserFace.setId(FtbUtil.getId());
            insertUserFace.setUserId(userId);
            insertUserFace.setFaceData(url);
            insertUserFace.setFaceDataThumbnail(url);
            attendanceUserFaceMapper.insert(insertUserFace);
        }
    }

    @Override
    public String changeImg() {

        // 查询用户照片
        LambdaQueryWrapper<AttendanceUserFace> userFaceQueryWrapper = new LambdaQueryWrapper<AttendanceUserFace>()
                .eq(AttendanceUserFace::getUserId, "471608060058616325")
                .eq(AttendanceUserFace::getDeleteMark, ConstantUtil.NUM_FALSE);
        AttendanceUserFace userFace = attendanceUserFaceMapper.selectOne(userFaceQueryWrapper);
        return changeBase64ToImage(userFace.getFaceData());
    }

    @Override
    public String machineClockIn(String sn, String userId, String tenantId, String clockInId) throws Exception {

        // 查询考勤机名称
        ClockInDto clockInDto = getClockInDto(sn, userId, tenantId);
        MutablePair<Integer, String> pair;
        if (StringUtils.isEmpty(clockInId)) {
            pair = attendanceClockInService.clockIn(clockInDto);
        } else {
            pair = attendanceClockInService.updateClockIn(clockInId, clockInDto);
        }
        return pair.getLeft().toString();
    }

    private ClockInDto getClockInDto(String sn, String userId, String tenantId) {

        String name = attendanceMachineManageMapper.getMachineName(sn);
        name = StringUtils.isEmpty(name) ? "未知设备" : name;
        return new ClockInDto(ConstantUtil.CLOCK_TYPE_NORMAL, sn, ConstantUtil.DEVICE_MACHINE, sn, name, userId, tenantId);
    }

    @Override
    public String clockIn(String sn, String userId, String tenantId) {

        String result = "0";
        Date today = new Date();
        try {
            UserInfo userInfo = new UserInfo();
            userInfo.setUserId(userId);
            userInfo.setTenantId(tenantId);
            List<GroupInfoVo> list = attendanceClockInService.getClockInMainInfo(new Date(), userInfo, ConstantUtil.NUM_TRUE);
            if (list.isEmpty()) {
                generateClockIn(today, sn, userId, tenantId);
                return "未找到可打卡班次";
            }
            AttendanceRuleVo rule = null;
            for (GroupInfoVo groupInfoVo : list) {
                rule = groupInfoVo.getAttendanceRuleList().stream().filter(v -> v.getCurrentPeriod().equals(ConstantUtil.NUM_TRUE)).findFirst().orElse(null);
                if (null != rule) {
                    break;
                }
            }
            if (null == rule) {
                // 查询当前时间在出勤规则上班时间 -> 下班缺卡时间段(不能为空)内, 下班打卡记录不为空, 取缺卡时间最大的一个
                List<AttendanceRuleVo> ruleList = new ArrayList<>();
                for (GroupInfoVo groupInfoVo : list) {
                    AttendanceRuleVo ruleCheck = groupInfoVo.getAttendanceRuleList().stream().filter(v -> null != v.getOutLackPoint() && DateDetail.checkTimeBetween(new Date(), v.getInPoint(), v.getOutLackPoint())).findFirst().orElse(null);
                    if (null != ruleCheck && null != ruleCheck.getOffWorkInfoVo()) {
                        ruleList.add(ruleCheck);
                    }
                }
                if (ruleList.isEmpty()) {
                    generateClockIn(today, sn, userId, tenantId);
                    return "未找到可打卡出勤规则";
                }
                rule = ruleList.stream().max(Comparator.comparing(AttendanceRuleVo::getOutLackPoint)).get();
            }
            if (null == rule.getOnWorkInfoVo()) {
                // 打上班卡
                result = machineClockIn(sn, userId, tenantId, null);
            }
            // 判定出勤规则类型
            boolean attendanceTypeCheck = checkAttendanceType(rule.getAttendanceType());
            if (null != rule.getOnWorkInfoVo() && null == rule.getOffWorkInfoVo()) {
                if (rule.getAttendanceType().equals(AttendanceTypeEnum.WORKOVERTIME.getCode())) {
                    // 加班超过上班时间无法更新打卡, 所以设置默认不允许迟到
                    rule.setLateEnable(ConstantUtil.NUM_FALSE);
                }
                // 更新上班卡
                if (attendanceTypeCheck && ConstantUtil.NUM_FALSE == rule.getLateEnable() && today.before(rule.getInPoint()) && !rule.getOnWorkInfoVo().getClockInStatus().equals(ClockInStatusEnum.NO_CLOCK.getValue())) {
                    if (!rule.getOnWorkInfoVo().getApprovalStatus().equals(ConstantUtil.PASS_APPROVAL) ||
                            (rule.getOnWorkInfoVo().getApprovalStatus().equals(ConstantUtil.PASS_APPROVAL) && rule.getOnWorkInfoVo().getApplyType().equals(ConstantUtil.APPLY_OUTSIDE))) {
                        // 不允许迟到, 更新上班打卡
                        result = machineClockIn(sn, userId, tenantId, rule.getOnWorkInfoVo().getClockInId());
                    }
                } else if (attendanceTypeCheck && ConstantUtil.NUM_TRUE == rule.getLateEnable() && today.before(rule.getLatePoint()) && !rule.getOnWorkInfoVo().getClockInStatus().equals(ClockInStatusEnum.NO_CLOCK.getValue())) {
                    if (!rule.getOnWorkInfoVo().getApprovalStatus().equals(ConstantUtil.PASS_APPROVAL) ||
                            (rule.getOnWorkInfoVo().getApprovalStatus().equals(ConstantUtil.PASS_APPROVAL) && rule.getOnWorkInfoVo().getApplyType().equals(ConstantUtil.APPLY_OUTSIDE))) {
                        // 不允许迟到, 更新上班打卡
                        result = machineClockIn(sn, userId, tenantId, rule.getOnWorkInfoVo().getClockInId());
                    }
                } else {
                    // 打下班卡
                    result = machineClockIn(sn, userId, tenantId, null);
                }
            }
            if (attendanceTypeCheck && null != rule.getOffWorkInfoVo() && !rule.getOffWorkInfoVo().getClockInStatus().equals(ClockInStatusEnum.NO_CLOCK.getValue())) {
                if (!rule.getOffWorkInfoVo().getApprovalStatus().equals(ConstantUtil.PASS_APPROVAL) ||
                        (rule.getOffWorkInfoVo().getApprovalStatus().equals(ConstantUtil.PASS_APPROVAL) && rule.getOffWorkInfoVo().getApplyType().equals(ConstantUtil.APPLY_OUTSIDE))) {
                    // 更新下班卡
                    result = machineClockIn(sn, userId, tenantId, rule.getOffWorkInfoVo().getClockInId());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("打卡失败，{}", e.getMessage());
            result = ConstantUtil.STR_FALSE;
        }
        // 打卡不成功也会生成打卡记录, 但没有打卡结果
        if (!result.equals("1") && !result.equals("2") && !result.equals("3")) {
            generateClockIn(today, sn, userId, tenantId);
        }
        return result;
    }

    @Override
    public void updateKeMiPhoto(String userId, String photoUrl, String tenantId) {

        List<UserBoundVO> list = v2UserApi.userListAndCopy(List.of(userId), null, tenantId);
        if (null == list || list.isEmpty()) {
            log.error("未找到用户信息, 用户no: {}", userId);
            return;
        }
        UserBoundVO userInfo = list.get(0);
        updateUserPhoto(photoUrl, userInfo.getId());
    }

    @Override
    @Machine(dealAction = ActionEnum.DA_KA, factory = MachineEnum.KE_MI)
    public void KeMiClockIn(UserTenantVo userTenant, String devId, String tenantId) {

        clockIn(devId, userTenant.getUserId(), tenantId);
    }

    private void generateClockIn(Date today, String sn, String userId, String tenantId) {
        ClockInDto clockInDto = getClockInDto(sn, userId, tenantId);
        FtbAttendanceClockIn clockIn = new FtbAttendanceClockIn();
        clockIn.setId(FtbUtil.getId());
        clockIn.setDay(today);
        clockIn.setUserId(userId);
        clockIn.setClockInTime(today);
        clockIn.setApprovalStatus(ConstantUtil.PASS_TRUE);
        clockIn.setClockInKind(clockInDto.getClockInKind());
        clockIn.setAddress(clockInDto.getAddress());
        clockIn.setDeviceType(clockInDto.getDeviceType());
        clockIn.setDeviceId(clockInDto.getDeviceId());
        clockIn.setDeviceName(clockInDto.getDeviceName());
        // 添加到数据库
        attendanceClockInService.save(clockIn);
    }

    private boolean checkAttendanceType(Integer attendanceType) {
        return attendanceType.equals(AttendanceTypeEnum.ORDINARY.getCode()) || attendanceType.equals(AttendanceTypeEnum.WORKOVERTIME.getCode());
    }

    private String changeBase64ToImage(String faceData) {

        if (StringUtils.isEmpty(faceData)) {
            return "";
        }
        // 解码base64
        String decode = URLDecoder.decode(faceData, StandardCharsets.UTF_8);
        String imgStr = decode.replaceFirst("data:image/jpeg;base64,", "");
        // 转换成图片
        String replace = imgStr.replaceAll("\r", "");
        String str = replace.replaceAll("\n", "");
        byte[] bytes = Base64.getDecoder().decode(str);
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        String url;
        try {
            BufferedImage image = ImageIO.read(byteArrayInputStream);
            byteArrayInputStream.close();
            String uuid = FtbUtil.getId();
            String path = fileApi.getPath(FileTypeConstant.TEMPORARY);
            String tmpPath = System.getProperty("java.io.tmpdir") + path;
            Path p = Paths.get(tmpPath);
            if (!Files.exists(p)) {
                Files.createDirectories(p);
            }
            String fileName = tmpPath + uuid + ".jpg";
            File outputFile = new File(fileName);
            ImageIO.write(image, "jpg", outputFile);
            MultipartFile multiFile = new MockMultipartFile(uuid, outputFile.getName(), MediaType.MULTIPART_FORM_DATA_VALUE, new FileInputStream(outputFile));
            FileInfo fileInfo = fileUploadApi.uploadFile(multiFile, path, multiFile.getName());
            url = fileInfo.getUrl().replace("/jnpf-resource-1304460613/", "");
            FileUtil.deleteFile(fileName);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return url;
    }
}
