package com.zd.forward.serivce;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zd.common.core.constant.Constants;
import com.zd.common.core.constant.SecurityConstants;
import com.zd.common.core.domain.R;
import com.zd.common.core.exception.ServiceException;
import com.zd.common.core.utils.ServletUtils;
import com.zd.common.core.utils.StringUtils;
import com.zd.common.redis.service.RedisService;
import com.zd.forward.config.AlgorithmYml;
import com.zd.forward.domain.AnalysisData;
import com.zd.forward.domain.DataPostAnalysisRespDto;
import com.zd.forward.domain.ImgPostResponse;
import com.zd.forward.domain.VideoRequestData;
import com.zd.forward.serivce.mqtt.CommonSend;
import com.zd.forward.utils.Base64DecodedMultipartFile;
import com.zd.forward.utils.FileUploadUtils;
import com.zd.forward.utils.HttpUtils;
import com.zd.system.api.RemoteFileService;
import com.zd.system.api.alarm.RemoteAlarmService;
import com.zd.system.api.alarm.domain.AlarmEntrty;
import com.zd.system.api.alarm.domain.Routes;
import com.zd.system.api.alarm.domain.SendTypes;
import com.zd.system.api.domain.Algorithm;
import com.zd.system.api.domain.SysFile;
import com.zd.system.api.laboratory.RemoteLaboratoryService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import sun.misc.BASE64Decoder;
import zd.common.mqtt.config.MessageBody;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.*;

import static com.zd.forward.utils.HttpUtils.*;

@Service
@RefreshScope
public class CheckService {
    Logger logger = LoggerFactory.getLogger(CheckService.class);
    @Resource(name = "restTemplateLocal")
    private RestTemplate restTemplateLocal;
    /**
     * 上传文件存储在本地的根路径
     */
    @Value("${file.path:/home/AIPIC}")
    private String localFilePath;

    @Autowired
    private AlgorithmYml algorithmYml;

    @Autowired
    private RedisService redisService;

    @Autowired
    private RemoteAlarmService remoteAlarmService;

    @Autowired
    private CommonSend commonSend;

    @Autowired
    private SendSginAccessLogService sendSginAccessLogService;

    @Autowired
    private RemoteFileService remoteFileService;

    @Autowired
    private RemoteLaboratoryService laboratoryService;


    private static final String warn = "违规操作告警：请{}，离开实验室！";

    /**
     * 合并检查 三合一套餐
     */
    public R checkAndCommit(Long id, MultipartFile file, Long subId) {
        try {
            int alarmNum = 0;
            //========= 请求超时验证部分开始 ===========
            //600 则代表退出验证流程 需要重新刷卡
//            R<Long> fail = getObjectR(id);
//            if (fail.getCode() != 200) {
//                return fail;
//            }
            //根据实验室id查询检查项
            R<Map<Object, Object>> subject = laboratoryService.getCheckInfo(subId);
            if (subject.getCode() != 200) {
                return subject;
            }
            Map<Object, Object> map = subject.getData();
            Object labCheckInObj = map.get("checkIn");
            if (StringUtils.isNull(labCheckInObj)) {
                return R.fail(700, "未配置检查项");
            }
            String labSkipped = "0";
            String labCheckCount = "2";
            Object labSkippedObj = map.get("skipped");
            if (StringUtils.isNotNull(labSkippedObj)) {
                labSkipped = String.valueOf(labSkippedObj);
            }
            Object labCheckCountObj = map.get("checkCount");
            if (StringUtils.isNotNull(labCheckCountObj)) {
                labCheckCount = String.valueOf(labCheckCountObj);
            }
            String labCheckIn = String.valueOf(labCheckInObj);
            String[] checkItem = labCheckIn.split(",");
            File toFile = multipartFileToFile(file);
            for (String code : checkItem) {
                //========= 获取算法INFO ===========
                AlgorithmYml.CheckValid checkValid = algorithmYml.getCheckValid(Integer.valueOf(code));
                MultiValueMap<String, Object> params = getStringObjectMultiValueMap(checkValid, String.valueOf(id));
                HttpEntity<MultiValueMap<String, Object>> files = getHttpEntityMap(toFile, params);
                logger.info("===============4=================");
                ImgPostResponse<DataPostAnalysisRespDto> send = HttpUtils.send(restTemplateLocal, files, algorithmYml);
                if (send == null || send.getStatus_code() != 1000) {
                    assert send != null;
                    logger.error(send.getMessage());
                    return R.fail("算法服务错误，请重试！");
                }
                logger.info("===============5=================");
                R algorithm = saveAlgorithm(send, checkValid);
                if (algorithm.getCode() != 200) return algorithm;
                DataPostAnalysisRespDto data = send.getData();

                Map<String, Object> result = (Map<String, Object>) data.getAnalysisDatas().get(0).getResult();
                Map<String, Object> algorithmData = (Map<String, Object>) result.get("algorithm_data");
                Map<String, Object> modelResult = (Map<String, Object>) result.get("model_data");
                List<Map<String, Object>> objects = (List<Map<String, Object>>) modelResult.get("objects");
                logger.info("============算法请求日志打印：算法ID：{},请求结果：{}", code, algorithmData.getOrDefault("is_alert", ""));
                //通过的
                if (algorithmData.getOrDefault("is_alert", "").toString().equals("false") && !objects.isEmpty()) {
                    alarmNum++;
                } else {
                    //不跳过
                    if (StringUtils.isNotEmpty(labSkipped) && labSkipped.equals("1")) {
                        //如果没有通过则次数加一
                        //键为前缀+签到id +下划线+验证类型
                        String key = Constants.SINGIN_CHECK_JUMP_KEY + id + "_" + code;
                        Long increment = redisService.redisTemplate.opsForValue().increment(key);
                        redisService.expire(key, Constants.SINGIN_OUT_TIME);
                        if (increment != null && increment >= Integer.parseInt(labCheckCount)) {
                            //黎晨这里让把跳过时状态码改为700，所以700的含义为检查失败并且跳过
                            return R.fail(700, "符合跳过条件执行跳过");
                        }
                    }
                    return R.fail(300, "算法识别未通过", code);
                }
                Boolean f = send.getStatus_code() == 1000;
                String msg = f ? "解析成功!" : "解析失败!";
                send(code, id, f, msg);
            }
            if (alarmNum == checkItem.length) {
                return R.ok();
            }
        } catch (Exception e) {
            logger.error(e.getMessage());
            e.printStackTrace();
        }
        return R.fail();
    }

    /**
     * @param id 进出记录ID
     */
    public R checkAndCommit(String code, MultipartFile file, Long id) {
        //========= 请求超时验证部分开始 ===========
        //600 则代表退出验证流程 需要重新刷卡
        R<Long> fail = getObjectR(id);
        if (fail.getCode() != 200) {
            return fail;
        }
        //========= 请求超时验证部分结束 ===========
        //========= 获取算法INFO ===========
        AlgorithmYml.CheckValid checkValid = algorithmYml.getCheckValid(Integer.valueOf(code));
        //=========发送验证信息到算法服务开始
        JSONObject send = null;
        try {
            send = send(file, checkValid, id);
        } catch (Exception e) {
            logger.error(e.getMessage());
            e.printStackTrace();
        }
        //=========发送验证信息到算法服务结束
        //=========算法服务返回结果验证开始
        R apply = R.fail("算法服务错误，请重试！");
        Boolean f = false;
        try {
            if (send == null || send.getInteger("status_code") != 1000L) {
                apply = R.fail("算法服务错误，请重试！");
                return apply;
            }
            Map<String, Object> result = null;
            try {
                result = send.getJSONObject("data").getJSONObject("result").getInnerMap();
            } catch (Exception e) {
                e.printStackTrace();
                apply = R.fail("算法服务返回数据错误，请联系管理员！");
                return apply;
            }
            //todo  获取 检测结果
            apply = checkValid.getCheckResultValid().apply(result);
            f = apply.getCode() == 200;
            if (!f && algorithmYml.getJumpThreshold() != -1) {
                //如果没有通过则次数加一
                //键为前缀+签到id +下划线+验证类型
                String key = Constants.SINGIN_CHECK_JUMP_KEY + id + "_" + code;
                Long increment = redisService.redisTemplate.opsForValue().increment(key);
                redisService.expire(key, Constants.SINGIN_OUT_TIME);
                if (increment >= algorithmYml.getJumpThreshold()) {
                    //黎晨这里让把跳过时状态码改为700，所以700的含义为检查失败并且跳过
                    apply.setCode(700);
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            apply = R.fail("算法服务返回数据错误，请联系管理员！");
            return apply;
        } finally {
//            //无论成功失败,插入记录数据,异步请求远程接口
            send(code, id, f, apply.getMsg());
//            //保存识别记录
//            String r = send == null ? null : send.toJSONString();
//           sendSginAccessLogService.saveAlgorithmResult(cereataAlgorithm(r, "image", f, apply.getMsg(), id, fail.getData()));
//            //持久化
//            Map<String, Object> data = (Map<String, Object>) send.get("data");
//           /* Object srcImage =  data.get("src_image");
//            String srcImg = this.generateImage(srcImage.toString(),algorithmYml.getImgTemp());*/
//            data.put("src_img", fileR.getData().getUrl());
//            data.put("type", "image");
//            data.put("algorithmName", checkValid.getAlgorithmName());
////            restTemplateLocal.postForEntity("http://192.168.1.17:9218/algorithm/save",data,String.class).getBody();
//            laboratoryService.saveData(Covert(data));
        }
        //=========算法服务返回结果验证结束
        logger.info(apply.toString());
        if (apply.getCode() == 200) {
            //加奖励分
            // laboratoryService.addRecord();
        }
        return apply;
    }


    /**
     * 三合一
     *
     * @param ids
     * @param file
     * @param codes
     * @return
     */
    public R checkInAll(Long[] ids, MultipartFile file[], String codes) {
        Map result = new HashMap();
        String[] codesArrs = codes.split(",");
        for (int i = 0; i < codesArrs.length; i++) {
            R r = checkAndCommit(codesArrs[i], file[i], ids[i]);
            AlgorithmYml.CheckValid checkValid = algorithmYml.getCheckValid(Integer.valueOf(codesArrs[i]));
            result.put(checkValid.getAlgoId(), r.getMsg() == null ? "通过" : r.getMsg());

        }
        return R.ok(result);
    }


    public Algorithm Covert(Map data) {
        Algorithm algorithm = new Algorithm();
        algorithm.setAlgorithmType(data.get("type").toString());
        algorithm.setAlgorithmResult(data.get("src_img").toString());
        algorithm.setSubId(Long.valueOf(data.get("aid").toString()));
        algorithm.setSignId(Long.valueOf(data.get("did").toString()));
        algorithm.setAlgorithmName(data.get("algorithmName").toString());
        algorithm.setIsAlarm(Integer.parseInt(data.get("isAlarm").toString()));
        algorithm.setStatus(0);
        return algorithm;
    }

    /**
     * @param algorithmResult
     * @param type
     * @param result
     * @param msg
     * @param signId          签到ID
     * @param subId           实验室ID
     * @return
     */
    public Algorithm cereataAlgorithm(String algorithmResult, String type, Boolean result, String msg, Long signId, Long subId) {
        Algorithm algorithm = new Algorithm();
        algorithm.setAlgorithmResult(algorithmResult);
        algorithm.setAlgorithmType(type);
        algorithm.setParseResult(result);
        algorithm.setParseResultMsg(msg);
        algorithm.setSignId(signId);
        algorithm.setSubId(subId);
        return algorithm;

    }

    /**
     * 视频检查
     *
     * @param checkValid
     * @param videoRequestData
     * @param subId
     */
    public void checkVideo(AlgorithmYml.VideoCheckValid checkValid, VideoRequestData videoRequestData, Long subId) {
        boolean f = false;
        R apply = R.fail("结果解析错误");
        try {
            apply = checkValid.getVideoCheckResultValid().apply(videoRequestData.getData(), subId);
            if (f = (apply.getCode() == 300)) {
                //发送预警
                MessageBody<String> messageBody = new MessageBody<>();
                messageBody.setData(CharSequenceUtil.format(warn, checkValid.getTips()));
                //给一体机发送mqtt消息
                commonSend.send("lab/news" + subId, messageBody, 0);
                //获取实验室负责人电话 需要远程获取，因为在内网，没有和实验室服务在一起， 不能用feign,需要手动通过网关请求
                String adminPhoneBySubId = sendSginAccessLogService.getAdminPhoneBySubId(subId, algorithmYml.getLoginUri());
                if (CharSequenceUtil.isNotBlank(adminPhoneBySubId)) {
                    AlarmEntrty alarmEntrty = new AlarmEntrty(Routes.NoticePush, new String[]{SendTypes.SMS.toString()}, adminPhoneBySubId);
                    //发送短信 通过feign 调用
                    remoteAlarmService.send(alarmEntrty);
                }
            }
        } finally {
            String json = JSON.toJSONString(videoRequestData);
            sendSginAccessLogService.saveAlgorithmResult(cereataAlgorithm(json, "vidoe", f, apply.getMsg(), null, subId));
            Map<String, Object> data = (Map<String, Object>) JSONObject.parse(json);
            Object srcImage = data.get("pic_data");
            String srcImg = this.generateImage(srcImage.toString(), algorithmYml.getImgTemp());
            data.put("src_img", srcImg);
            data.put("type", "video");
            laboratoryService.saveData(Covert(data));
        }
    }

    /**
     * 发送警报
     */
    public void sendAlarm(VideoRequestData videoRequestData) {
        sendSginAccessLogService.saveAlarm(videoRequestData);
    }

    /**
     * 发送警报
     */
    public void playMp3() {
        sendSginAccessLogService.playMp3();
    }


    private R<Long> getObjectR(Long id) {
        Long subId = redisService.getCacheObject(Constants.SINGIN_id_KEY + id);
        if (subId == null) {
            return R.fail(600, "签到&签出已超时，请重新刷卡重试！");
        }
        //刷新key
        boolean expire = redisService.expire(Constants.SINGIN_id_KEY + id, 120);
        if (!expire) {
            return R.fail(600, "签到&签出已超时，请重新刷卡重试！");
        }
        return R.ok(subId);
    }

    //给黎晨用的模拟方法 他让给他mock个方法测试
    public R mockTest(String code, MultipartFile file, Long id) {
        R<Long> objectR = getObjectR(id);
        if (objectR.getCode() != 200) {
            return objectR;
        }
        //随机成功或失败
        Boolean f = RandomUtil.randomInt(0, 2) == 0;
        //无论成功失败,插入记录数据,异步请求远程接口
        send(code, id, f, "");
        if (f) {
            return R.ok();
        }
        //如果没有通过则次数加一
        Map<String, Boolean> map = new HashMap(1);
        //键为前缀+签到id +下划线+验证类型
        String key = Constants.SINGIN_CHECK_JUMP_KEY + id + "_" + code;
        Long increment = redisService.redisTemplate.opsForValue().increment(key);
        redisService.expire(key, Constants.SINGIN_OUT_TIME);
        Boolean isJump = increment >= algorithmYml.getJumpThreshold();
        map.put("jump", isJump);
        R<Object> fail = R.fail();
        fail.setData(map);
        return fail;

    }


    public void send(String code, Long id, Boolean f, String msg) {
        String token = Objects.requireNonNull(ServletUtils.getRequest()).getHeader(SecurityConstants.TOKEN_AUTHENTICATION);
        if (CharSequenceUtil.isBlank(token)) {
            throw new ServiceException("无权限！");
        }
        sendSginAccessLogService.sendAddLogRest(code, id, f, msg, token, algorithmYml.getLoginUri() + algorithmYml.getCheckLogUrl());
    }

    /**
     * 获取算法请求地址
     *
     * @param checkValid
     * @param id
     * @return
     */
    private String getPostUrl(AlgorithmYml.CheckValid checkValid, Long id) {
        //设置请求体，注意是LinkedMultiValueMap
        MultiValueMap<String, Object> params = getStringObjectMultiValueMap(checkValid, String.valueOf(id));
        Set<String> keySet = params.keySet();
        StringBuffer stb = new StringBuffer();
        for (String key : keySet) {
            stb.append("&" + key + "=" + params.get(key).get(0).toString());
        }
        String paramUrl = algorithmYml.getImgPostUrl() + String.valueOf(stb).replaceFirst("&", "?");
        logger.info(paramUrl);
        return paramUrl;
    }

    /**
     * 图片算法请求参数
     *
     * @param file
     * @param checkValid
     * @return
     * @throws IOException
     */

    public JSONObject send(MultipartFile file, AlgorithmYml.CheckValid checkValid, Long id) throws IOException {
        //设置请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        File uploadFile = null;
        try {
            // 文件本地存储收集
            FileUploadUtils.upload(localFilePath, file);
            //MultipartFile 转为临时文件
            uploadFile = multipartFileToFile(file);
            //文件转为文件系统资源
            FileSystemResource fileSystemResource = new FileSystemResource(uploadFile);
            //设置请求体，注意是LinkedMultiValueMap
            MultiValueMap<String, Object> params = getStringObjectMultiValueMap(checkValid, String.valueOf(id));

            MultiValueMap<String, Object> form = getStringObjectMultiValueMap(fileSystemResource);
            params.addAll(form);
            //用HttpEntity封装整个请求报文
            HttpEntity<MultiValueMap<String, Object>> files = new HttpEntity<>(params, headers);
            String paramUrl = algorithmYml.getImgPostUrl();
            logger.info(paramUrl);
            JSONObject body = restTemplateLocal.postForObject(paramUrl, files, JSONObject.class);
            logger.info(body.toJSONString());
            return body;
        } finally {
            //删除临时文件
//            if (uploadFile != null) {
//                uploadFile.delete();
//            }
        }
    }

    /**
     * base64转图片/视频
     *
     * @param imgStr      base64位图片
     * @param imgFilePath 路径
     * @return
     */
    public String generateImage(String imgStr, String imgFilePath) {// 对字节数组字符串进行Base64解码并生成图片
        BASE64Decoder decoder = new BASE64Decoder();
        try {
            // Base64解码
            byte[] bytes = decoder.decodeBuffer(imgStr);
            for (int i = 0; i < bytes.length; ++i) {
                if (bytes[i] < 0) {// 调整异常数据
                    bytes[i] += 256;
                }
            }
            // 生成jpeg图片
            imgFilePath += "/" + UUID.randomUUID() + ".jpeg";
            OutputStream out = new FileOutputStream(imgFilePath);
            out.write(bytes);
            out.flush();
            out.close();
            return imgFilePath;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 存储数据
     *
     * @param send
     * @param checkValid
     */
    private R saveAlgorithm(ImgPostResponse<DataPostAnalysisRespDto> send, AlgorithmYml.CheckValid checkValid) {
        DataPostAnalysisRespDto analysisRespDto = send.getData();
        List<AnalysisData> analysisDatas = analysisRespDto.getAnalysisDatas();
        if (!analysisDatas.isEmpty()) {
            for (AnalysisData data : analysisDatas) {
                String picture = data.getRet_image();
                String header = "data:image/jpeg;base64," + picture;
                MultipartFile multipartFile = Base64DecodedMultipartFile.base64ToMultipart(header);
                R<SysFile> sysFileR = remoteFileService.upload(multipartFile);
                String imageUrl = sysFileR.getData().getUrl();
                Map<String, Object> result = (Map<String, Object>) data.getResult();
                Map<String, Object> map = new HashMap<>();
                Map<String, Object> algorithmData = (Map<String, Object>) result.get("algorithm_data");
                Map<String, Object> modelResult = (Map<String, Object>) result.get("model_data");
                List<Map<String, Object>> objects = (List<Map<String, Object>>) modelResult.get("objects");
                if (algorithmData.getOrDefault("is_alert", "").toString().equals("false") && objects.size() > 0) {
                    map.put("isAlarm", 0);
                } else {
                    map.put("isAlarm", 1);
                }
                map.put("did", checkValid.getDid());
                map.put("aid", checkValid.getAlgoId());
                map.put("src_img", imageUrl);
                map.put("type", "image");
                map.put("algorithmName", checkValid.getAlgorithmName());
                return laboratoryService.saveData(Covert(map));
            }
        }
        logger.error("接口数据返回异常");
        throw new ServiceException("接口数据返回异常");
    }
}
