package jnpf.attendance.service.impl;

import cn.hutool.core.bean.BeanUtil;
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 com.fantaibao.permission.handling.PermissionHandling;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.collect.Maps;
import jnpf.attendance.mapper.AttendanceGroupMapper;
import jnpf.attendance.mapper.AttendanceUserFaceMapper;
import jnpf.attendance.mapper.FtbAttendanceFaceChangeLogMapper;
import jnpf.attendance.service.AttenceMachineService;
import jnpf.attendance.service.AttendanceUserFaceService;
import jnpf.attendance.service.AttendanceUserService;
import jnpf.attendance.service.UserFaceService;
import jnpf.base.ActionResult;
import jnpf.base.UserInfo;
import jnpf.constant.FileTypeConstant;
import jnpf.entity.AttendanceGroupUser;
import jnpf.entity.attendance.AttendanceMachineManage;
import jnpf.entity.attendance.AttendanceRuleScope;
import jnpf.entity.attendance.AttendanceUserFace;
import jnpf.entity.attendance.FtbAttendanceFaceChangeLog;
import jnpf.enums.attendance.ScopeBizType;
import jnpf.exception.HandleException;
import jnpf.file.FileApi;
import jnpf.file.FileUploadApi;
import jnpf.model.attendance.dto.FaceChangeQueryDto;
import jnpf.model.attendance.dto.FaceQueryDto;
import jnpf.model.attendance.dto.MachineDealDto;
import jnpf.model.attendance.dto.UserDto;
import jnpf.model.attendance.vo.*;
import jnpf.model.attendance.vo.attendance.GroupMiniVo;
import jnpf.permission.UserApi;
import jnpf.permission.V2UserApi;
import jnpf.permission.eum.v2.UserWorkStatusEnums;
import jnpf.permission.model.user.BaseUserInfoVo;
import jnpf.permission.vo.v2.user.UserBoundVO;
import jnpf.util.*;
import jnpf.util.attendance.RuleScopeUtil;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
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 java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 人脸识别服务实现
 *
 * @author yanwenfu
 * @create 2025-04-08
 */
@Slf4j
@Service
public class UserFaceServiceImpl implements UserFaceService {

    @Resource
    private AttendanceUserFaceService attendanceUserFaceService;
    @Resource
    private AttendanceUserFaceMapper attendanceUserFaceMapper;
    @Resource
    private FtbAttendanceFaceChangeLogMapper ftbAttendanceFaceChangeLogMapper;
    @Resource
    private AttendanceUserService attendanceUserService;
    @Resource
    private AttendanceGroupMapper attendanceGroupMapper;
    @Autowired
    private UserApi userApi;
    @Resource
    private V2UserApi v2UserApi;
    @Resource
    private FileUploadApi fileUploadApi;
    @Resource
    private FileApi fileApi;
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private PermissionHandling permissionHandling;
    @Autowired
    private RuleScopeUtil ruleScopeUtil;
    @Autowired
    private AttenceMachineService attenceMachineService;

    @Override
    public Boolean getPhotoCheck(MultipartFile file, UserInfo userInfo) throws HandleException {

        /*
        // 查询用户数据库人脸信息
        UserFaceVo userFace = attendanceUserFaceService.getUserFace(userInfo.getUserId());
        if (null == userFace) {
            throw new HandleException("请先上传人脸");
        }
        // 获取特征
        float[] image1;
        if (StringUtils.isNotEmpty(userFace.getFaceFeature())) {
            JSONArray array = JSONUtil.parseArray(userFace.getFaceFeature());
            image1 = new float[array.size()];
            for (int i = 0; i < array.size(); i++) {
                image1[i] = NumberUtils.safeToFloat(array.get(i));
            }
        } else {
            image1 = SeetaFaceUtil.analyzeEcognizer(null, userFace.getFaceData());
            LambdaUpdateWrapper<AttendanceUserFace> updateWrapper = new LambdaUpdateWrapper<AttendanceUserFace>()
                    .eq(AttendanceUserFace::getUserId, userFace.getUserId())
                    .eq(AttendanceUserFace::getDeleteMark, ConstantUtil.NUM_FALSE)
                    .set(AttendanceUserFace::getFaceFeature, JSONUtil.toJsonStr(image1));
            attendanceUserFaceMapper.update(null, updateWrapper);
        }
        float[] image2 = SeetaFaceUtil.analyzeEcognizer(file, null);
        // 开始比对
        return SeetaFaceUtil.compareEigenvalue(image1, image2);
        */
        return Boolean.TRUE;
    }

    @Override
    public Integer sendToMachine(String userId) throws Exception {

        log.error("数据库执行完成 ... , {}", DateDetail.getDate2Str(new Date(), DateDetail.DF4));
        // 下发设备
        MachineDealDto machineDealDto = new MachineDealDto(userId, ConstantUtil.CAL_ADD);
        List<String> machineList = attendanceUserFaceService.machineDeal(machineDealDto);
        // 等待考勤机回复等待结果
        return syncMachineDealResult(machineList, userId);
    }

    @Transactional(rollbackFor = Exception.class)
    public void updateMachineSyncStatus(String userId, Integer result) {
        log.info("考勤机同步完成 ... , {}", DateDetail.getDate2Str(new Date(), DateDetail.DF4));
        LambdaUpdateWrapper<AttendanceUserFace> updateFace = new LambdaUpdateWrapper<AttendanceUserFace>()
                .eq(AttendanceUserFace::getUserId, userId)
                .eq(AttendanceUserFace::getDeleteMark, ConstantUtil.NUM_FALSE)
                .set(AttendanceUserFace::getSyncStatus, result);
        attendanceUserFaceMapper.update(null, updateFace);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveUserFace(UserDto userDto, UserInfo userInfo, String faceUrl, String thumbnailUrl) {

        LambdaUpdateWrapper<AttendanceUserFace> updateWrapper = null;
        String faceId;
        UserFaceVo userFace = attendanceUserFaceService.getUserFace(userDto.getUserId());
        if (userFace != null) {
            updateWrapper = new LambdaUpdateWrapper<AttendanceUserFace>()
                    .eq(AttendanceUserFace::getUserId, userDto.getUserId())
                    .eq(AttendanceUserFace::getDeleteMark, ConstantUtil.NUM_FALSE)
                    .set(AttendanceUserFace::getLastModifyUserId, userInfo.getUserId())
                    .set(AttendanceUserFace::getLastModifyTime, DateUtil.getNowDate());
        }
        if (updateWrapper != null) {
            faceId = userFace.getId();
            updateWrapper
                    .set(AttendanceUserFace::getFaceData, faceUrl)
                    .set(AttendanceUserFace::getFaceDataThumbnail, thumbnailUrl);
            attendanceUserFaceMapper.update(null, updateWrapper);
        } else {
            AttendanceUserFace face = new AttendanceUserFace();
            face.setId(FtbUtil.getId());
            face.setUserId(userDto.getUserId());
            face.setFaceData(faceUrl);
            face.setFaceDataThumbnail(thumbnailUrl);
            face.setCreatorUserId(userInfo.getUserId());
            face.setLastModifyUserId(userInfo.getUserId());
            face.setLastModifyTime(DateUtil.getNowDate());
            faceId = face.getId();
            attendanceUserFaceMapper.insert(face);
        }
        // 变动记录
        BaseUserInfoVo user = userApi.getUserInfoById(userDto.getUserId());
        FtbAttendanceFaceChangeLog changeLog = new FtbAttendanceFaceChangeLog(
                userDto.getUserId(),
                faceId,
                "[" + user.getUserName() + "]已录入人脸",
                userInfo.getUserId()
        );
        ftbAttendanceFaceChangeLogMapper.insert(changeLog);
    }

    private String uploadFile(File file, String suffix) throws IOException {

        FileInputStream input = new FileInputStream(file);
        String uuid = UUID.randomUUID().toString();
        MultipartFile multiFile = new MockMultipartFile(uuid + TemplateExcelUtils.SUFFIX, file.getName(), MediaType.MULTIPART_FORM_DATA_VALUE, input);
        input.close();
        FileInfo fileInfo = fileUploadApi.uploadFileCustomName(multiFile, fileApi.getPath(FileTypeConstant.TEMPORARY), uuid + suffix);
        return fileInfo.getUrl().replace(ConstantUtil.EXTRA_PATH, "");
    }

    private Integer syncMachineDealResult(List<String> machineList, String userId) throws Exception {

        log.error("等待考勤机回复：{}, 考勤机列表：{}", userId, machineList);
        int returnResult = ConstantUtil.NUM_FALSE;
        if (machineList.isEmpty()) {
            return returnResult;
        }
        List<String> waitMachines = new ArrayList<>(machineList);
        int index = 0;
        while (index < 15) {
            // 警告！！！警告！！！产品要求 在同步中获取异步产生的结果 最多等待30秒
            Thread.sleep(2000L);
            Iterator<String> it = waitMachines.iterator();
            while (it.hasNext()) {
                String machine = it.next();
                Object strObj = redisUtil.getString(String.format(ConstantUtil.MACHINE_MESSAGE, machine) + userId);
                if (null != strObj) {
                    JSONObject json = JSONUtil.parseObj(strObj);
                    int result = Integer.parseInt(json.get(machine).toString());
                    if (result == ConstantUtil.NUM_TRUE) {
                        it.remove();
                        redisUtil.remove(String.format(ConstantUtil.MACHINE_MESSAGE, machine) + userId);
                    }
                }
            }
            if (waitMachines.isEmpty()) {
                returnResult = ConstantUtil.NUM_TRUE;
                break;
            }
            index += 1;
        }
        log.error("返回结果：{}", returnResult);
        return returnResult;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteUserFace(String userId, UserInfo userInfo) throws Exception {

        UserFaceVo userFace = attendanceUserFaceService.getUserFace(userId);
        if (null == userFace) {
            throw new HandleException("未找到您的人脸数据");
        }
        attendanceUserFaceService.deleteUserFace(userId);
        BaseUserInfoVo user = userApi.getUserInfoById(userId);
        String msg;
        if (userId.equals(userInfo.getUserId())) {
            msg = "[" + user.getUserName() + "]已清空人脸信息";
        } else {
            msg = "管理员[" + userInfo.getUserName() + "]已清空[" + user.getUserName() + "]人脸信息";
        }
        FtbAttendanceFaceChangeLog log = new FtbAttendanceFaceChangeLog(userId, msg, userInfo.getUserId());
        ftbAttendanceFaceChangeLogMapper.insert(log);
        // 删除考勤机数据
        MachineDealDto machineDealDto = new MachineDealDto(userId, ConstantUtil.CAL_DELETE);
        attendanceUserFaceService.machineDeal(machineDealDto);
    }

    @Override
    public UserFaceVo getUserFace(String userId) {

        return attendanceUserFaceService.getUserFace(userId);
    }

    @Override
    public PageInfo<ChangeLogVo> getChangeLogPage(FaceChangeQueryDto queryDto) {

        List<String> userIds = null;
        String tenantId = UserProvider.getUser().getTenantId();
        if (StringUtils.isNotEmpty(queryDto.getUserName())) {
            List<UserBoundVO> list = v2UserApi.userListAndCopyLikeName(queryDto.getUserName(), null, tenantId);
            userIds = new ArrayList<>();
            if (null != list && !list.isEmpty()) {
                if (StringUtils.isNotEmpty(queryDto.getOrgId())) {
                    userIds.addAll(list.stream().filter(v -> v.getOrganizeId().equals(queryDto.getOrgId())).map(UserBoundVO::getId).collect(Collectors.toList()));
                } else {
                    userIds.addAll(list.stream().map(UserBoundVO::getId).collect(Collectors.toList()));
                }
            }
        } else {
            if (StringUtils.isNotEmpty(queryDto.getOrgId())) {
                userIds = new ArrayList<>();
                ActionResult<List<UserBoundVO>> actionResult = v2UserApi.listTargetOrganizesOrHaveChild(List.of(queryDto.getOrgId()), false, List.of(UserWorkStatusEnums.RESIGNED), tenantId);
                if (null != actionResult && actionResult.getCode() == 200 && null != actionResult.getData() && !actionResult.getData().isEmpty()) {
                    List<UserBoundVO> list = actionResult.getData();
                    List<String> collect = list.stream().map(UserBoundVO::getId).collect(Collectors.toList());
                    userIds.addAll(collect);
                }
            }
        }
        if (null != userIds && userIds.isEmpty()) {
            return new PageInfo<>();
        }
        PageHelper.startPage(queryDto.getCurrentPage(), queryDto.getPageSize());
        PageInfo<ChangeLogVo> page = new PageInfo<>(ftbAttendanceFaceChangeLogMapper.getChangeLogList(queryDto.getStartDate(), queryDto.getEndDate(), userIds));
        if (page.getList().isEmpty()) {
            return page;
        }
        List<String> collect = page.getList().stream().map(ChangeLogVo::getCreatorUserId).collect(Collectors.toList());
        List<UserBoundVO> list = v2UserApi.userListAndCopy(collect, false, UserProvider.getUser().getTenantId());
        if (null != list && !list.isEmpty()) {
            Map<String, UserBoundVO> userMap = list.stream().collect(Collectors.toMap(UserBoundVO::getId, Function.identity()));
            page.getList().forEach(v -> {
                UserBoundVO user = userMap.get(v.getCreatorUserId());
                if (null != user) {
                    v.setCreatorUserName(user.getUserName());
                    v.setPostName(user.getPositionName());
                }
            });
        }
        return page;
    }

    @Override
    public PageInfo<UserFaceDetailVo> getUserFacePage(FaceQueryDto queryDto) {

        // 查询考勤组下的成员
        String[] split = StringUtil.isEmpty(queryDto.getGroupId()) ? null : queryDto.getGroupId().split(",");
        List<String> groupIdlist = null == split ? new ArrayList<>() : Arrays.stream(split).collect(Collectors.toList());
        // 需要查询人脸的用户信息
        List<AttendanceGroupUserVo> userList = new ArrayList<>();
        Map<String, UserBoundVO> userMap = null;
        Map<String, GroupMiniVo> groupMap;
        // 根据权限过滤用户
        UserInfo loginUser = UserProvider.getUser();
        List<String> filterUserList = new ArrayList<>();
        if (!loginUser.getIsAdministrator()) {
            List<String> collectUserList = permissionHandling.getUserIdsByUserId(loginUser.getUserId());
            // collectUserList 为 null 等同于 超级管理员 不进行过滤
            if (null != collectUserList) {
                if (collectUserList.isEmpty()) {
                    // 可查看的人为empty
                    return new PageInfo<>();
                }
                filterUserList.addAll(collectUserList);
            }
        }
        if (groupIdlist.contains("-1")) {
            groupIdlist.remove("-1");
            // 查询没有考勤组的用户
            ActionResult<List<UserBoundVO>> actionResult = v2UserApi.getAllUserInfoBatch(List.of(), UserProvider.getUser().getTenantId());
            List<UserBoundVO> userBoundList = actionResult.getData();
            userMap = userBoundList.stream().filter(v -> !v.getWorkStatusEnums().equals(UserWorkStatusEnums.RESIGNED)).collect(Collectors.toMap(UserBoundVO::getId, Function.identity()));
            // 查询所有考勤组用户
            List<AttendanceGroupUser> allGroupUserList = attendanceUserService.queryAllForNotSecondment();
            if (null != allGroupUserList && !allGroupUserList.isEmpty()) {
                List<String> allUser = new ArrayList<>(userMap.keySet());
                List<String> collect = allGroupUserList.stream().map(AttendanceGroupUser::getUserId).collect(Collectors.toList());
                if (!filterUserList.isEmpty()) {
                    allUser.removeIf(v -> !filterUserList.contains(v));
                }
                allUser.removeAll(collect);
                allUser.forEach(userId -> userList.add(new AttendanceGroupUserVo(userId)));
            }
        }
        Date today = new Date();
        List<AttendanceGroupUser> queryList = new ArrayList<>();
        if (!groupIdlist.isEmpty()) {
            groupIdlist = groupIdlist.stream().distinct().collect(Collectors.toList());
            List<AttendanceGroupUser> groupUserList = attendanceUserService.queryByUsersAndGroupFilterSecondment(today, today, null, groupIdlist, Boolean.FALSE);
            if (null != groupUserList && !groupUserList.isEmpty()) {
                queryList.addAll(groupUserList);
            }
        }
        // 有班组, 进行班组人员过滤
        if (StringUtils.isNotEmpty(queryDto.getTeamId())) {
            String[] teamArray = queryDto.getTeamId().split(",");
            ActionResult<List<UserBoundVO>> actionResult = v2UserApi.listTargetOrganizesOrHaveChild(List.of(teamArray), false, List.of(UserWorkStatusEnums.RESIGNED), loginUser.getTenantId());
            if (null != actionResult && 200 == actionResult.getCode()) {
                List<String> orgIds = actionResult.getData().stream().map(UserBoundVO::getOrganizeId).distinct().collect(Collectors.toList());
                // 根据组织id查询考勤组
                List<AttendanceGroupVo> orgGroupList = attendanceGroupMapper.getGroupListByOrgId(null, orgIds);
                List<String> orgGroupIds = orgGroupList.stream().map(AttendanceGroupVo::getId).collect(Collectors.toList());
                List<String> teamUserList = actionResult.getData().stream().map(UserBoundVO::getId).collect(Collectors.toList());
                Set<String> collect = queryList.stream().map(AttendanceGroupUser::getUserId).collect(Collectors.toSet());
                List<String> queryUsers = new ArrayList<>();
                teamUserList.forEach(teamUser -> {
                    if (collect.add(teamUser)) {
                        queryUsers.add(teamUser);
                    }
                });
                if (!queryUsers.isEmpty()) {
                    List<AttendanceGroupUser> groupUserList = attendanceUserService.queryByUsersAndGroupFilterSecondment(today, today, queryUsers, orgGroupIds, Boolean.FALSE);
                    queryList.addAll(groupUserList);
                }
            }
        }
        // 过滤没有权限的用户
        if (!filterUserList.isEmpty()) {
            queryList.removeIf(v -> !filterUserList.contains(v.getUserId()));
        }
        if (queryList.isEmpty()) {
            return new PageInfo<>();
        }
        List<AttendanceGroupUserVo> gUserList = BeanUtil.copyToList(queryList, AttendanceGroupUserVo.class);
        Set<String> groupIds = gUserList.stream().map(AttendanceGroupUserVo::getGroupId).collect(Collectors.toSet());
        List<GroupMiniVo> groupList = attendanceGroupMapper.getAllGroupByLevel(new ArrayList<>(groupIds));
        groupMap = groupList.stream().collect(Collectors.toMap(GroupMiniVo::getGroupId, Function.identity()));
        List<String> userIds = queryList.stream().map(AttendanceGroupUser::getUserId).collect(Collectors.toList());
        if (null == userMap) {
            ActionResult<List<UserBoundVO>> actionResult = v2UserApi.getAllUserInfoBatch(userIds, UserProvider.getUser().getTenantId());
            List<UserBoundVO> userBoundList = actionResult.getData();
            userMap = userBoundList.stream().filter(v -> !v.getWorkStatusEnums().equals(UserWorkStatusEnums.RESIGNED)).collect(Collectors.toMap(UserBoundVO::getId, Function.identity()));
        }
        List<String> filterUser = new ArrayList<>(userMap.keySet());
        gUserList.removeIf(v -> !filterUser.contains(v.getUserId()));
        userList.addAll(gUserList);
        // 用户是否绑定了考勤机
        List<String> queryUserIds = userList.stream()
                .map(AttendanceGroupUserVo::getUserId)
                .collect(Collectors.toList());
        List<AttendanceRuleScope> scopeList = ruleScopeUtil.selectUserEffectListBatch(
                queryUserIds,
                ScopeBizType.ATTENDANCE_MACHINE,
                ConstantUtil.NUM_FALSE
        );
        // 提前准备 Map 避免多次查询
        Map<String, List<AttendanceRuleScope>> scopeMap = scopeList.isEmpty()
                ? Collections.emptyMap()
                : scopeList.stream().collect(Collectors.groupingBy(AttendanceRuleScope::getUserId));
        Map<String, AttendanceMachineManage> machineMap = Collections.emptyMap();
        if (!scopeList.isEmpty()) {
            List<String> ruleIds = scopeList.stream()
                    .map(AttendanceRuleScope::getRuleId)
                    .distinct()
                    .collect(Collectors.toList());
            List<AttendanceMachineManage> machineList = attenceMachineService.list(new LambdaQueryWrapper<AttendanceMachineManage>()
                    .in(AttendanceMachineManage::getId, ruleIds)
                    .eq(AttendanceMachineManage::getDeleteMark, ConstantUtil.NUM_FALSE));
            if (!machineList.isEmpty()) {
                machineMap = machineList.stream().collect(Collectors.toMap(
                        AttendanceMachineManage::getId,
                        Function.identity()
                ));
            }
        }
        // 遍历用户，填充属性
        for (AttendanceGroupUserVo v : userList) {
            String userId = v.getUserId();
            // 绑定考勤机逻辑
            List<AttendanceRuleScope> userScopes = scopeMap.getOrDefault(userId, Collections.emptyList());
            boolean hasMachine = userScopes.stream()
                    .map(AttendanceRuleScope::getRuleId)
                    .distinct()
                    .anyMatch(machineMap::containsKey);
            v.setBindingMachine(hasMachine);
            // 设置组信息
            Optional.ofNullable(groupMap.get(v.getGroupId()))
                    .ifPresent(group -> v.setGroupName(group.getGroupName()));
            // 设置用户信息
            Optional.ofNullable(userMap.get(userId))
                    .ifPresent(userBound -> {
                        v.setRealName(userBound.getUserName());
                        v.setOrgName(userBound.getOrganizeName());
                        v.setPositionName(userBound.getPositionName());
                        v.setWorkStatus(userBound.getWorkStatusName());
                    });
        }
        // 根据考勤组成员查询人脸信息
        PageHelper.startPage(queryDto.getCurrentPage(), queryDto.getPageSize());
        return new PageInfo<>(attendanceUserFaceMapper.getUserFaceList(queryDto, userList));
    }

    @Override
    public Map<String, List<AttendanceMachineManage>> getUserFaceList(List<String> userIds, String tenantId) {
        List<AttendanceRuleScope> scopeList = ruleScopeUtil.selectUserEffectListBatch2(
                userIds,
                ScopeBizType.ATTENDANCE_MACHINE,
                ConstantUtil.NUM_FALSE,
                tenantId
        );
        // 提前准备 Map 避免多次查询
        Map<String, List<String>> scopeMap = scopeList.isEmpty()
                ? Collections.emptyMap()
                : scopeList.stream().collect(Collectors.groupingBy(AttendanceRuleScope::getUserId, Collectors.mapping(AttendanceRuleScope::getRuleId, Collectors.toList())));
        Map<String, AttendanceMachineManage> machineMap = Maps.newHashMap();
        if (!scopeList.isEmpty()) {
            List<String> ruleIds = scopeList.stream()
                    .map(AttendanceRuleScope::getRuleId)
                    .distinct()
                    .collect(Collectors.toList());
            List<AttendanceMachineManage> machineList = attenceMachineService.list(new LambdaQueryWrapper<AttendanceMachineManage>()
                    .in(AttendanceMachineManage::getId, ruleIds)
                    .eq(AttendanceMachineManage::getDeleteMark, ConstantUtil.NUM_FALSE));
            if (!machineList.isEmpty()) {
                machineMap.putAll(machineList.stream().collect(Collectors.toMap(
                        AttendanceMachineManage::getId,
                        Function.identity()
                )));
            }
        }
        Map<String, List<AttendanceMachineManage>> userMachineMap = Maps.newHashMap();
        scopeMap.forEach((userId, ruleIds) -> {
            userMachineMap.put(userId, machineMap.values().stream().filter(v -> ruleIds.contains(v.getId())).collect(Collectors.toList()));
        });
        return userMachineMap;
    }

    @Override
    public Boolean hasUserFace(String userId) {

        UserFaceVo userFace = getUserFace(userId);
        return null != userFace;
    }

    @Override
    public FaceMiniVo getUserFaceInfo(String userId) {

        UserFaceVo userFace = getUserFace(userId);
        if (null == userFace) {
            return null;
        }
        return new FaceMiniVo(userFace.getUserId(), userFace.getFaceData(), DateDetail.getDate2Str(userFace.getLastModifyTime(), DateDetail.DF4));
    }

    @Override
    public UserFaceVo getUserFaceDetail(String id) {

        AttendanceUserFace userFace = attendanceUserFaceMapper.selectById(id);
        if (null == userFace) {
            return null;
        }
        return JsonUtil.getJsonToBean(userFace, UserFaceVo.class);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void initializationFace(String tenantId) {
        // 查询face表中有多少个没有缩略图的数据
        List<UserFaceDetailVo> userFaceDetailVos = attendanceUserFaceMapper.getNoThumbnail();
        if (null == userFaceDetailVos || userFaceDetailVos.isEmpty()) {
            log.error(tenantId + "没有需要初始化的人脸数据");
//            return "没有需要初始化的人脸数据";
        }
        int num = 0;
        for (UserFaceDetailVo userFaceDetailVo : Objects.requireNonNull(userFaceDetailVos)) {
            // 下载图片到临时文件
            File outputFile = null;
            File thumbnailFile = null;
            try {
                // 截取链接的文件格式
                String suffix = userFaceDetailVo.getFaceData().substring(userFaceDetailVo.getFaceData().lastIndexOf("."));
                outputFile = downloadImageToTempFile(userFaceDetailVo.getFaceData(), suffix);
//                thumbnailFile = downloadImageToTempFile(userFaceDetailVo.getFaceDataThumbnail(), suffix);
                thumbnailFile = Files.createTempFile("thumbnail-", suffix).toFile();
                Thumbnails.of(outputFile)
                        .scale(0.5)
                        .outputQuality(0.5)
                        .toFile(thumbnailFile);
                String thumbnailUrl = uploadFile(thumbnailFile, suffix);
                userFaceDetailVo.setFaceDataThumbnail(thumbnailUrl);
                num++;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        SqlSession sqlSession = null;
        try {
            // 1. 获取批处理模式的 SqlSession
            sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false); // 关闭自动提交
            // 将userFaceDetailVos 500个分一组
            for (int i = 0; i < userFaceDetailVos.size(); i++) {
                UserFaceDetailVo userFaceDetailVo = userFaceDetailVos.get(i);
                attendanceUserFaceMapper.updateUserFaceThumbnail(userFaceDetailVo);
                // 每处理 500 条提交一次
                if (i % 500 == 0 && i > 0) {
                    sqlSession.commit();
                    sqlSession.clearCache(); // 清理缓存避免内存占用
                }
            }
            // 4. 统一提交事务
            sqlSession.commit();

        } catch (Exception e) {
            if (sqlSession != null) {
                sqlSession.rollback(); // 回滚事务
            }
            throw new RuntimeException("Batch update failed", e);
        } finally {
            if (sqlSession != null) {
                sqlSession.close(); // 关闭会话
            }
        }
        log.error(tenantId + "初始化成功，共初始化 " + num + " 个人脸缩略数据");
//        return "初始化成功，共初始化 " + num + " 个人脸缩略数据";
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public AttendanceUserFace updateUserFaceInfo(String userId, String updateUserId) {

        // 查询用户关联的人脸
        LambdaQueryWrapper<AttendanceUserFace> faceWrapper = new LambdaQueryWrapper<AttendanceUserFace>()
                .eq(AttendanceUserFace::getDeleteMark, ConstantUtil.NUM_FALSE)
                .eq(AttendanceUserFace::getUserId, userId);
        AttendanceUserFace userFace = attendanceUserFaceMapper.selectOne(faceWrapper);
        if (null == userFace) {
            return null;
        }
        userFace.setLastModifyTime(DateUtil.getNowDate());
        userFace.setLastModifyUserId(updateUserId);
        attendanceUserFaceMapper.updateById(userFace);
        // 生成变动记录
        // FtbAttendanceFaceChangeLog log = new FtbAttendanceFaceChangeLog(user.getUserId(), userFace.getId(), user.getRealName() + "已录入人脸", updateUser.getUserId());
        // ftbAttendanceFaceChangeLogMapper.insert(log);
        return userFace;
    }

    /**
     * 从 URL 下载图片到临时文件
     * @param imageUrl 图片的 URL 地址
     * @return 临时文件路径 (Path 对象)
     * @throws IOException 如果下载或文件操作失败
     */
    public static File downloadImageToTempFile(String imageUrl, String suffix) throws IOException {
        // 1. 创建临时文件（自动生成唯一文件名）
        Path tempFile = Files.createTempFile("download-", suffix);

        // 2. 打开 URL 连接并获取输入流
        try (InputStream in = new URL(imageUrl).openStream()) {
            // 3. 将输入流内容复制到临时文件
            Files.copy(in, tempFile, StandardCopyOption.REPLACE_EXISTING);
        }

        return tempFile.toFile();
    }
}
