package com.github.microservice.auth.server.core.service.local;

import com.github.microservice.auth.client.content.ResultContent;
import com.github.microservice.auth.client.content.ResultState;
import com.github.microservice.auth.server.core.conf.RedisearchConf;
import com.github.microservice.auth.server.core.model.RedisearchResultModel;
import com.github.microservice.auth.client.model.UserFaceQueryModel;
import com.github.microservice.auth.client.model.UserFaceUploadModel;
import com.github.microservice.auth.client.service.UserFaceService;
import com.github.microservice.auth.server.core.dao.UserDao;
import com.github.microservice.auth.server.core.dao.UserFaceDao;
import com.github.microservice.auth.server.core.domain.User;
import com.github.microservice.auth.server.core.domain.UserFace;
import com.github.microservice.auth.server.core.model.ResultsModel;
import com.github.microservice.auth.server.core.util.FaceUtil;
import com.github.microservice.core.util.JsonUtil;
import com.github.microservice.core.util.bean.BeanUtil;
import com.mongodb.client.gridfs.GridFSFindIterable;
import com.mongodb.client.gridfs.model.GridFSFile;
import jakarta.validation.constraints.NotNull;
import lombok.Cleanup;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.kafka.common.protocol.types.Field;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import redis.clients.jedis.JedisPooled;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.http.HttpResponse;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.List;

@Slf4j
@Service
public class UserFaceServiceImpl implements UserFaceService {

    @Autowired
    UserDao userDao;

    @Autowired
    GridFsTemplate gridFsTemplate;

    @Autowired
    UserFaceDao userFaceDao;

    @Autowired
    RestTemplate restTemplate;

    @Value("${faceImage.width}")
    private int width;

    @Value("${faceImage.height}")
    private int height;
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    RedisearchConf redisearchConf;

    /**
     * 上传人脸图片
     *
     * @param userFaceUploadModel
     * @return
     */
    @SneakyThrows
    public ResultContent<String> upload(UserFaceUploadModel userFaceUploadModel) {

        if (userFaceUploadModel.getFile().isEmpty()) {
            return ResultContent.build(ResultState.Fail);
        }

        User user = userDao.findTop1ById(userFaceUploadModel.getUserId());
        if (user == null) {
            return ResultContent.build(ResultState.UserNotExists);
        }
        boolean isUpdate = StringUtils.isNotBlank(userFaceUploadModel.getUpdateFaceFileId());
        if (isUpdate) {
            //替换原人脸图片
            if (userFaceDao.existsByUserIdAndFaceFSId(userFaceUploadModel.getUserId(), userFaceUploadModel.getUpdateFaceFileId())) {
                return ResultContent.build(ResultState.FaceNotExists);
            }
        }

        MultipartFile file = userFaceUploadModel.getFile();
        InputStream inputStream = file.getInputStream();


        //TODO 调整图片大小
//        byte[] bytes = imageResizer(userFaceUploadModel.getUserFace());
        //TODO 上传人脸库，获取向量
        HttpResponse<String> response = FaceUtil.uploadFs("http://172.24.50.53:5001/v2/represent", inputStream, "yunet");

        if (response.statusCode() != 200) {
            return ResultContent.build(ResultState.Fail, "人脸特征提取失败");
        }

        final String body = response.body();

        ResultsModel  resultsModel = JsonUtil.toObject(body, ResultsModel.class);
        System.out.println(resultsModel);



        System.out.println(body);
        List<Double> embedding = resultsModel.getResults().get(0).getEmbedding();



        JedisPooled jedis = new JedisPooled(redisearchConf.getHost(), redisearchConf.getPort(), null, redisearchConf.getPassword());

        // 动态设置变量
        String indexName = redisearchConf.getIndexName();
        String prefix = redisearchConf.getPrefix();
        String fieldName = redisearchConf.getFieldName();
        String dim = redisearchConf.getDim();
        initIndex(jedis, indexName, prefix, fieldName, dim);


        // 将 List<Float> 转换为 float[]
        double[] vector = listToFloatArray(embedding);
        // 归一化向量
        double norm = calculateNorm(vector);
        for (int i = 0; i < vector.length; i++) {
            vector[i] /= norm;
        }

        // 转换为字节数组 (FLOAT32，每个值占 4 字节)
        ByteBuffer buffer = ByteBuffer.allocate(vector.length * Double.BYTES);
        for (double value : vector) {
            buffer.putDouble(value);
        }

        String key = prefix + user.getId() + System.currentTimeMillis();

        long hset = jedis.hset(key.getBytes(), fieldName.getBytes(), buffer.array());

        System.out.println(hset);
        jedis.close();
        //图片入库
        String fileId = gridFsTemplate.store(inputStream, UUID.randomUUID().toString(), Map.of("userId", userFaceUploadModel.getUserId())).toHexString();
        UserFace userFace = new UserFace();
        if (isUpdate) {
            userFace = userFaceDao.findTopByUserIdAndFaceFSId(userFaceUploadModel.getUserId(), userFaceUploadModel.getUpdateFaceFileId());
        }
        //向量入库
        userFace.setUserId(user.getId());
        userFace.setFaceFSId(fileId);
        //设置向量
        userFace.setVector(embedding);
        userFace.setFaceDataKey(key);
        userFaceDao.save(userFace);
        return ResultContent.buildContent(fileId);
    }

    private static void initIndex(JedisPooled jedis, String indexName, String prefix, String fieldName, String dim) {
        try {
            Map<String, Object> stringObjectMap = jedis.ftInfo(indexName);
            //TODO 检查参数是否正确
        }catch (Exception e) {
            //创建索引
            // Lua 脚本模板
            String createIndexScript = "local indexName = KEYS[1] " +
                    "local prefix = KEYS[2] " +
                    "local fieldName = KEYS[3] " +
                    "local dim = ARGV[1] " +
                    "redis.call('FT.CREATE', indexName, 'ON', 'HASH', 'PREFIX', '1', prefix, " +
                    "'SCHEMA', fieldName, 'VECTOR', 'FLAT', '6', " +
                    "'TYPE', 'FLOAT64', 'DIM', dim, 'DISTANCE_METRIC', 'COSINE')";


            String a = """
                    
                   """;

                // 执行 Lua 脚本并传递变量
                Object result = jedis.eval(createIndexScript,
                        // KEYS 参数：索引名、前缀、字段名
                        List.of(indexName, prefix, fieldName), List.of(dim));
                System.out.println("Index created successfully: " + result);
        }
    }


    private static double calculateNorm(double[] vector) {
        double sum = 0;
        for (double v : vector) {
            sum += v * v;  // 对每个分量平方求和
        }
        return (double) Math.sqrt(sum);  // 求平方和的平方根
    }

    public static double[] listToFloatArray(List<Double> list) {
        // 创建一个与 List 大小相同的 float 数组
        double[] array = new double[list.size()];

        // 使用普通的 for 循环将 List 中的每个元素放入数组中
        for (int i = 0; i < list.size(); i++) {
            array[i] = list.get(i);  // 自动拆箱，将 Float 转换为 float
        }

        return array;
    }

    /**
     * 删除用户指定人脸图片
     *
     * @param userId
     * @param faceFileId
     * @return
     */
    public ResultContent delete(@NotNull String userId, @NotNull String faceFileId) {
        User user = userDao.findTop1ById(userId);
        if (user == null) {
            return ResultContent.build(ResultState.UserNotExists);
        }

        UserFace userFace = userFaceDao.findTopByUserIdAndFaceFSId(userId, faceFileId);
        if (userFace == null) {
            return ResultContent.build(ResultState.FaceNotExists);
        }

        //删除人脸库的人脸
        JedisPooled jedis = new JedisPooled(redisearchConf.getHost(), redisearchConf.getPort(), null, redisearchConf.getPassword());

        long del = jedis.del(userFace.getFaceDataKey());
        if (del == 0) {
            log.error("人脸库删除数据失败：key:{}", userFace.getFaceDataKey());
            return ResultContent.build(ResultState.Fail);
        }
        userFaceDao.deleteById(userFace.getId());
        return ResultContent.build(ResultState.Success);
    }

    /**
     * 获取用户人脸认证图片
     *
     * @param userId
     * @return
     */
    @SneakyThrows
    public ResultContent<List<UserFaceQueryModel>> get(String userId) {
        //初始化返回结果
        List<UserFaceQueryModel> userFaceBase64List = new ArrayList<>();

        List<UserFace> userFaceList = userFaceDao.findByUserId(userId);

        List<String> facePic = userFaceList.stream().map(UserFace::getFaceFSId).toList();

        if (!facePic.isEmpty()) {
            Query query = new Query(Criteria.where("_id").in(facePic));
            GridFSFindIterable gridFSFiles = gridFsTemplate.find(query);
            for (GridFSFile gridFS : gridFSFiles) {
                @Cleanup InputStream inputStream = gridFsTemplate.getResource(gridFS).getContent();
                UserFaceQueryModel userFaceQueryModel = new UserFaceQueryModel();
                String userFace = convertInputStreamToBase64(inputStream);
                userFaceQueryModel.setFaceFileId(gridFS.getObjectId().toHexString());
                userFaceQueryModel.setUserFace(userFace);
                userFaceBase64List.add(userFaceQueryModel);
            }
        }
        return ResultContent.buildContent(userFaceBase64List);
    }

    /**
     * 对比人脸图片
     *
     * @param
     * @return
     */
    @SneakyThrows
    public ResultContent<Object> matches(MultipartFile file) {
        //TODO 上传人脸服务，获取向量
        InputStream inputStream = file.getInputStream();

        //TODO 调整图片大小
//        byte[] bytes = imageResizer(userFaceUploadModel.getUserFace());
        //TODO 上传人脸库，获取向量
        HttpResponse<String> response = FaceUtil.uploadFs("http://172.24.50.53:5001/v2/represent", inputStream, "yunet");

        if (response.statusCode() != 200) {
            return ResultContent.build(ResultState.Fail, "人脸特征提取失败");
        }

        final String body = response.body();

        ResultsModel  resultsModel = JsonUtil.toObject(body, ResultsModel.class);
        System.out.println(resultsModel);



        System.out.println(body);
        List<Double> embedding = resultsModel.getResults().get(0).getEmbedding();
        double[] vector = listToFloatArray(embedding);

        double norm = calculateNorm(vector);
        for (int i = 0; i < vector.length; i++) {
            vector[i] /= norm;
        }

        // 转换为字节数组 (FLOAT32，每个值占 4 字节)
        ByteBuffer buffer = ByteBuffer.allocate(vector.length * Double.BYTES);
        for (double value : vector) {
            buffer.putDouble(value);
        }
        // 打印字节数组的内容
        byte[] queryVector = buffer.array();
        System.out.println("queryVector.length");
        System.out.println(queryVector.length);

        // 定义查询参数
        String indexName = redisearchConf.getIndexName();
        String prefix = redisearchConf.getPrefix();
        String fieldName = redisearchConf.getFieldName();
        String dim = redisearchConf.getDim();
//        String queryVectorString = "\x8d5\xf7=\x91w:=*\x19h=\x05u\xc8=\xfa\x06\xb0=+\x00\xa8=\x96\x9b\x02>\xd5\xb4\x19>\xf1Pf=\xb5U\xf7=\xbc\xaf\x10>\xd9\x96\x18>\xa5hk=\xcd\xd3\x83=\x86\xcd\x18>\xbd\x80\x0b>A$\xf1=\xb2\xcf\xc5=K\xd4\xe2=\x83c\x01>\x02\x85\xbb=c$'=\x00-\x14>\x0c[i=\x80\x0b\xaf=\x15r\x0c>\xb2\x1f\xbf<\xe78\x8e=\x9b\xb9\xdf=\xce\xd9\xa3=\xe7\xaa\r>\xee\\\xaa=\x83\x95g=g/R=x\xc0\x0b>\xfd\xe7\x07>M\xb8\x0f=\x05\xbcA=q\x0e\xbd=R\x98\x8c;b\xfe\x85=\xea%+=\xe7\x98\x81:0H\x07>\xd9g\x04>\xe5\xe3\x8e=oM]=%\xbe\x92<\x1e\xf7\x05>\x11\xa9\x1a>\xce1\xe9<Z\x8d\x0e>\xe0<\x07>\xcar\x86=\xde\x0e\x9e=l\x8e\xf6=\xa9\x04\xa7=7\xe9G=\n[\x05<\xcd\xb3\x1c=\xf26\xe2=\xfd\xbd\xa4=\x94\x95\xea=\xb3\x9f\xa7<*Z\xfb;o\xbb\xb7<\xae\xea\xdb=\x03\x82\xcb<\xc8\x12\x1a<\xfa\xf9\x91<\xacJ\x18=\x18\xcb\xb0=n\x87\x92=6j\xf0=\xa7\xf0\x92<]!\xb4=\xf8\x01\x85=\xaf\xfa\xd7=\xbbE\xe5;f\x9d\xeb<^\xe1\x00>\xa3\xb9\x93=\x83\xcb\x14>\xdb\x86\x1a>\xe1\xa0\xd7=Cu*=9\xfd\x8f=e\xe2\xe7<y1.=2T\x91=59\x92=\xd8\xb5\xbd=\xd5\xf1\xa1=\xf4\xd6\x08>m\xd3K<\x1b\xc5M=\x93#\xb2=\xc0\x8b\xb4=\xa0Ph=\xe4s\x92=\xd7\x93\xb4=\xa3x\\;Q\x84\x08>\x8b&\xdc<a\x0e\x1d=\x83\x9c\x9e<\x9a\x84\xac<\xceS\xdf=\x8bT\xab;\xa7\xfd\r=\x88j\x10<\xcd\x07\xee<\x07\x8e\x81<t^\xdc=\xcf\xcf\xbd=\xeb\x92\xa1<[\x88\xd7=\xf0\xbb\x00>\x8e*\x18>\x82\xad\x11>yG\xbb<g\x07\x06>\xa08\x8d<\x01C\x1b>\xc7\xfd:=\x0b\x93\x10;\xbce>=\xb9\xdba=";
        String topK = "1";  // 返回Top 10个结果
//        String vectorField = "doc_embedding";  // 向量字段名称

        String queryScript  = "local indexName = KEYS[1] "
                + "local queryVector = ARGV[1] "
                + "local topK = tonumber(ARGV[2]) "
                + "local vectorField = ARGV[3] "
                + "local query = '*=>[KNN ' .. topK .. ' @' .. vectorField .. ' $BLOB AS score]' "
                + "local result = redis.call('FT.SEARCH', indexName, query, "
                + "'PARAMS', '2', 'BLOB', queryVector, "
                + "'RETURN', '1', 'score', "
                + "'SORTBY', 'score', "
                + "'DIALECT', '2') "
                + "return result";
        // 执行Redis查询


        JedisPooled jedis = new JedisPooled( redisearchConf.getHost(), redisearchConf.getPort(), null, redisearchConf.getPassword());

        // 动态设置变量

        initIndex(jedis, indexName, prefix, fieldName, dim);

        Object result = jedis.eval(queryScript.getBytes(), 1, indexName.getBytes(), queryVector, topK.getBytes(), fieldName.getBytes());
        // 关闭连接
        jedis.close();
        // 打印结果
        System.out.println("Query Result: " + JsonUtil.toJson(result));
        RedisearchResultModel redisearchResultModel = processQueryResult(result);
        //TODO 解析结果，返回userId
        if (redisearchResultModel.getCount() == 0L) {
            return ResultContent.build(ResultState.FaceNoMatches);
        }

        if (redisearchResultModel.getScoreValue().equals("nan") || redisearchResultModel.getScoreValue().equals("-nan")) {
            return ResultContent.build(ResultState.FaceNoMatches);
        }

        BigDecimal scoreValue = new BigDecimal(redisearchResultModel.getScoreValue());
        if (scoreValue.compareTo(BigDecimal.ZERO) == 0 || scoreValue.compareTo(new BigDecimal(redisearchConf.getScore())) > 0) {
            String dataKey = redisearchResultModel.getDataKey();
            dataKey = dataKey.replace(prefix, "");
            String userId = dataKey.substring(0, 24);
            redisearchResultModel.setUserId(userId);
            return ResultContent.buildContent(BeanUtil.bean2Map(redisearchResultModel));
        }else {
            return ResultContent.build(ResultState.FaceNoMatches);
        }

    }


    private static RedisearchResultModel processQueryResult(Object result) {
        RedisearchResultModel redisearchResultModel = new RedisearchResultModel();

        if (result instanceof List){
            List<Object> list = (List) result;
            for (Object item : list) {
                if (item instanceof byte[]) {// data key
                    byte[] bytes = ((byte[]) item);
                    System.out.println("Found vector: " + new String(bytes)); // 可以根据需要进一步处理
                    redisearchResultModel.setDataKey(new String(bytes));
                } else if (item instanceof Long){// 返回的结果数
                    System.out.println("Found Long: " + item);
                    redisearchResultModel.setCount((Long)item);
                }else if (item instanceof List) {// 对应的 距离
                    List<Object> itemList = (List) item;
                    for (Object item1 : itemList) {
                        if (item1 instanceof byte[]){
                            byte[] bytes = ((byte[]) item1);
                            System.out.println("Found vector: " + new String(bytes)); // 可以根据需要进一步处理
                            String str = new String(bytes);
                            if (str.equals("score")){
                                redisearchResultModel.setScoreKey(str);
                            }else {
                                redisearchResultModel.setScoreValue(str);
                            }
                        }
                    }
                }
            }
        }
        return redisearchResultModel;
    }

    /**
     * 将 InputStream 转换为 Base64 字符串
     *
     * @param inputStream 输入流
     * @return Base64 编码的字符串
     * @throws IOException 如果发生 I/O 错误
     */
    @SneakyThrows
    public static String convertInputStreamToBase64(InputStream inputStream) {
        @Cleanup ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }
        byte[] bytes = outputStream.toByteArray();
        return Base64.getEncoder().encodeToString(bytes);
    }


    private byte[] imageResizer(String base64Image) {
        // 解码 Base64 字符串为 BufferedImage
        BufferedImage originalImage = decodeBase64ToImage(base64Image);

        // 调整图像大小
        BufferedImage resizedImage = resizeImage(originalImage, width, height);

        // 将调整后的图像转换为字节组
        return encodeImageToBytes(resizedImage);

    }

//    public static void resizeImage(InputStream inputStream, int width, int height) throws IOException {
//        BufferedImage inputImage = ImageIO.read(inputStream);
//
//        // 创建一个输出的 BufferedImage
//        BufferedImage outputImage = new BufferedImage(width, height, inputImage.getType());
//
//        // 使用 Graphics2D 绘制缩放后的图片
//        Graphics2D g2d = outputImage.createGraphics();
//        g2d.drawImage(inputImage, 0, 0, width, height, null);
//        g2d.dispose();
//
//        // 输出到文件
//        String formatName = outputFile.getName().substring(outputFile.getName().lastIndexOf(".") + 1);
//        ImageIO.write(outputImage, formatName, outputFile);
//    }

    private static BufferedImage resizeImage(BufferedImage originalImage, int width, int height) {
        BufferedImage resizedImage = new BufferedImage(width, height, originalImage.getType());
        Graphics2D g = resizedImage.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g.drawImage(originalImage, 0, 0, width, height, null);
        g.dispose();

        return resizedImage;
    }

    @SneakyThrows
    private static BufferedImage decodeBase64ToImage(String base64Image) {
        byte[] imageBytes = Base64.getDecoder().decode(base64Image);
        @Cleanup ByteArrayInputStream bais = new ByteArrayInputStream(imageBytes);
        return ImageIO.read(bais);
    }

    @SneakyThrows
    private static byte[] encodeImageToBytes(BufferedImage image) {
        @Cleanup ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(image, "png", baos);
        return baos.toByteArray();
    }
}
