package jnpf.attendance.service.impl;

import cn.xuyanwu.spring.file.storage.FileInfo;
import cn.xuyanwu.spring.file.storage.MockMultipartFile;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jnpf.attendance.service.UserFaceService;
import jnpf.attendance.service.UserFaceTxService;
import jnpf.base.UserInfo;
import jnpf.constant.FileTypeConstant;
import jnpf.entity.attendance.AttendanceUserFace;
import jnpf.file.FileApi;
import jnpf.file.FileUploadApi;
import jnpf.model.attendance.dto.MachineDealDto;
import jnpf.model.attendance.dto.UserDto;
import jnpf.util.*;
import jnpf.util.attendance.ImageCompressUtil;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

/**
 * 人脸服务实现
 *
 * @author yanwenfu
 * @create 2025-12-11
 */
@Slf4j
@Service
public class UserFaceTxServiceImpl implements UserFaceTxService {

    @Resource
    private UserFaceService userFaceService;
    @Resource
    private FileUploadApi fileUploadApi;
    @Resource
    private FileApi fileApi;

    @Override
    public Integer uploadUserFace(MultipartFile mFile, UserDto userDto, UserInfo userInfo) throws Exception {

        log.error("init ... , {}", DateDetail.getDate2Str(new Date(), DateDetail.DF4));
        String suffix = Objects.requireNonNull(mFile.getOriginalFilename())
                .substring(mFile.getOriginalFilename().lastIndexOf("."));
        File sourceFile = new File(
                System.getProperty("java.io.tmpdir"),
                "src_" + System.nanoTime() + suffix
        );
        String endWith = ".jpg";
        File firstFile = Files.createTempFile("source-", endWith).toFile();
        File outputFile = Files.createTempFile("face-", endWith).toFile();
        File thumbnailFile = Files.createTempFile("thumb-", endWith).toFile();
        try {
            // 接收原图
            mFile.transferTo(sourceFile);
            Thumbnails.of(sourceFile)
                    .useExifOrientation(true)
                    .scale(1.0)
                    .outputFormat("jpg")
                    .toFile(firstFile);
            // 压缩
            log.info("人脸压缩 ... , {}", DateDetail.getDate2Str(new Date(), DateDetail.DF4));
            ImageCompressUtil.compressFace(firstFile, outputFile, 400);
            log.info("缩略图压缩 ... , {}", DateDetail.getDate2Str(new Date(), DateDetail.DF4));
            ImageCompressUtil.compressThumbnail(outputFile, thumbnailFile, 40);
            log.info("压缩完成 ... , {}", DateDetail.getDate2Str(new Date(), DateDetail.DF4));
            // 上传 OSS
            String url = uploadFile(outputFile, endWith);
            String thumbnailUrl = uploadFile(thumbnailFile, suffix);
            log.info("上传完成 ... , {}", DateDetail.getDate2Str(new Date(), DateDetail.DF4));
            // 保存人脸信息
            userFaceService.saveUserFace(userDto, userInfo, url, thumbnailUrl);
            // 下发考勤机
            Integer result = userFaceService.sendToMachine(userDto.getUserId());
            // 更新结果
            userFaceService.updateMachineSyncStatus(userDto.getUserId(), result);
            log.error("执行结束：{}", result);
            return result;
        } finally {
            sourceFile.delete();
            outputFile.delete();
            thumbnailFile.delete();
        }
    }

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

        Integer result = ConstantUtil.NUM_FALSE;
        UserInfo updateUser = UserProvider.getUser();
        AttendanceUserFace userFace = userFaceService.updateUserFaceInfo(userId, updateUser.getUserId());
        if (null == userFace) {
            return result;
        }
        // 下发至考勤机
        result = userFaceService.sendToMachine(userFace.getUserId());
        // 更新结果
        userFaceService.updateMachineSyncStatus(userFace.getUserId(), result);
        return result;
    }

    public void compressForFace(File input, File output, long targetKB) throws Exception {

        long targetBytes = targetKB * 1024;

        if (input.length() <= targetBytes) {
            Files.copy(input.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING);
            return;
        }

        BufferedImage img = ImageIO.read(input);
        int width = img.getWidth();
        int height = img.getHeight();

        int maxSide = 1024;
        double scale = (width > maxSide || height > maxSide)
                ? Math.min(maxSide / (double) width, maxSide / (double) height)
                : 1.0;

        File scaledTemp = new File(
                System.getProperty("java.io.tmpdir"),
                "face_scaled_" + System.nanoTime() + ".jpg"
        );

        try {
            Thumbnails.of(input)
                    .scale(scale)
                    .outputQuality(1.0)
                    .toFile(scaledTemp);

            int maxLoop = calcLoopCount(input.length() / 1024, targetKB);

            binaryCompressAccurate(
                    scaledTemp, output, targetBytes,
                    0.5, 0.95, 0.85, maxLoop
            );

        } finally {
            scaledTemp.delete();
        }
    }

    public void compressForThumbnail(File input, File output, long targetKB) throws Exception {

        long targetBytes = targetKB * 1024;

        if (input.length() <= targetBytes) {
            Files.copy(input.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING);
            return;
        }

        BufferedImage img = ImageIO.read(input);
        int width = img.getWidth();
        int height = img.getHeight();

        int maxSide = 300;
        double scale = (width > maxSide || height > maxSide)
                ? Math.min(maxSide / (double) width, maxSide / (double) height)
                : 1.0;

        File scaledTemp = new File(
                System.getProperty("java.io.tmpdir"),
                "thumb_scaled_" + System.nanoTime() + ".jpg"
        );

        try {
            Thumbnails.of(input)
                    .scale(scale)
                    .outputQuality(1.0)
                    .toFile(scaledTemp);

            int maxLoop = calcLoopCount(input.length() / 1024, targetKB);

            binaryCompressAccurate(
                    scaledTemp, output, targetBytes,
                    0.35, 0.65, 0.5, maxLoop
            );

        } finally {
            scaledTemp.delete();
        }
    }

    private void binaryCompressAccurate(File input, File output,
                                        long targetBytes,
                                        double minQ,
                                        double maxQ,
                                        double startQ,
                                        int maxLoop) throws Exception {
        double quality = startQ;
        for (int i = 0; i < maxLoop; i++) {
            Thumbnails.of(input)
                    .scale(1.0)
                    .outputQuality(quality)
                    .toFile(output);
            long size = output.length();
            double ratio = size * 1.0 / targetBytes;
            // 已经非常接近目标
            if (ratio > 0.8 && ratio < 1.2) {
                return;
            }
            if (size > targetBytes) {
                maxQ = quality;
            } else {
                minQ = quality;
            }
            quality = (minQ + maxQ) / 2;
        }
        // 最后一轮兜底：如果压过头太多，回弹一次
        if (output.length() < targetBytes * 0.7) {
            double reboundQ = Math.min(maxQ, quality * 1.15);
            Thumbnails.of(input)
                    .scale(1.0)
                    .outputQuality(reboundQ)
                    .toFile(output);
        }
    }

    /**
     * 动态计算循环次数
     * @param inputKB 原图大小
     * @param targetKB 目标大小
     */
    private int calcLoopCount(long inputKB, long targetKB) {
        double ratio = inputKB * 1.0 / targetKB;
        if (ratio > 10) {       // 原图比目标大 > 10 倍
            return 5;
        } else if (ratio > 5) { // 5~10 倍
            return 4;
        } else if (ratio > 2) { // 2~5 倍
            return 3;
        } else {                 // 小图
            return 2;
        }
    }

    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, "");
    }
}
