package com.jsowell.service;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.jsowell.common.constant.CacheConstants;
import com.jsowell.common.constant.Constants;
import com.jsowell.common.core.redis.RedisCache;
import com.jsowell.common.enums.uniapp.OccupyOrderStatusEnum;
import com.jsowell.common.util.DateUtils;
import com.jsowell.common.util.StringUtils;
import com.jsowell.common.util.file.AliyunOssUploadUtils;
import com.jsowell.common.util.file.ImageUtils;
import com.jsowell.common.util.sign.MD5Util;
import com.jsowell.netty.server.mqtt.BootNettyMqttChannelInboundHandler;
import com.jsowell.pile.domain.OrderPileOccupy;
import com.jsowell.pile.domain.PileCameraInfo;
import com.jsowell.pile.dto.GenerateOccupyOrderDTO;
import com.jsowell.pile.dto.QueryOccupyOrderDTO;
import com.jsowell.pile.dto.camera.Camera2GroundLockCommand;
import com.jsowell.pile.dto.camera.CameraHeartBeatDTO;
import com.jsowell.pile.dto.camera.CameraIdentifyResultsDTO;
import com.jsowell.pile.service.MemberBasicInfoService;
import com.jsowell.pile.service.OrderPileOccupyService;
import com.jsowell.pile.service.PileCameraInfoService;
import com.jsowell.pile.vo.uniapp.customer.MemberVO;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 相机管理系统 Service
 *
 * @author Lemon
 * @Date 2023/12/5 11:11:32
 */
@Service
public class CameraService {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private PileCameraInfoService pileCameraInfoService;

    @Autowired
    private OrderPileOccupyService orderPileOccupyService;

    @Autowired
    private BootNettyMqttChannelInboundHandler handler;

    @Autowired
    private MemberBasicInfoService memberBasicInfoService;


    /**
     * 接收相机识别结果
     * @param jsonObject
     * @throws InterruptedException
     */
    public void receiveIdentifyResults(JSONObject jsonObject) throws InterruptedException {
        // 区分入场和出场
        Integer parking_state = 0;
        try {
            parking_state = jsonObject.getJSONObject("parking").getInteger("parking_state");
        }catch (Exception e) {
            logger.info("相机发送系统 alarmInfo:{}", jsonObject.toJSONString());
        }
        if (parking_state == Constants.one) {
            // 入场
            String parkingState = "ENTRY";
            String result = vehicleEntry(jsonObject, parkingState);
            logger.info("车辆入场处理 result:{}", result);
        }
        if (parking_state == Constants.two) {
            // 在场
        }
        if (parking_state == Constants.four) {
            // 出场
            vehicleLeave(jsonObject);
        }

        // saveInfo2DataBase(jsonObject);

    }


    /**
     * 车辆离场
     * @param jsonObject
     */
    private void vehicleLeave(JSONObject jsonObject) {
        // 将信息存数据库
        Map<String, String> resultMap = saveInfo2DataBase(jsonObject);
        if (resultMap == null) {
            logger.error("车辆离场，将信息存入数据库 error, 源数据：{}", jsonObject);
            return;
        }
        String plateNumber = resultMap.get("plateNumber");
        // 查出该车牌对应的挂起状态的占桩订单
        QueryOccupyOrderDTO dto = QueryOccupyOrderDTO.builder()
                .plateNumber(plateNumber)
                .orderStatus(OccupyOrderStatusEnum.ORDER_HANG_UP.getCode())
                .build();
        List<OrderPileOccupy> orderPileOccupyList = orderPileOccupyService.queryOccupyOrderList(dto);
        if (CollectionUtils.isEmpty(orderPileOccupyList)) {
            return;
        }
        OrderPileOccupy occupy = orderPileOccupyList.get(0);

        // 停止占桩订单
        orderPileOccupyService.stopOccupyPileOrder(occupy);
    }

    /**
     * 保存心跳到Redis
     * @param dto
     */
    public void saveHeartBeat2Redis(CameraHeartBeatDTO dto) {
        // 将基本信息存入缓存
        String redisKey = CacheConstants.CAMERA_HEARTBEAT + dto.getIp();
        redisCache.setCacheObject(redisKey, dto.getSn());
    }

    /**
     * 车辆入场
     * @param jsonObject json报文对象
     * @param parkingState
     * @return
     * @throws InterruptedException
     */
    private String vehicleEntry(JSONObject jsonObject, String parkingState) throws InterruptedException {
        // 先将车牌图片信息存入缓存
        // boolean result = saveCarPicture2Redis(jsonObject, parkingState);

        // 将信息存数据库
        Map<String, String> resultMap = saveInfo2DataBase(jsonObject);
        if (resultMap == null) {
            logger.error("车辆入场，将信息存入数据库 error, 源数据：{}", jsonObject);
            return null;
        }
        String sn = resultMap.get("sn");
        String zoneId = resultMap.get("zoneId");
        String plateNumber = resultMap.get("plateNumber");
        String devName = resultMap.get("devName");

        if (StringUtils.equals("无牌车", plateNumber)) {
            return null;
        }

        // 先判断该车牌是否有挂起未支付的占桩订单
        OrderPileOccupy occupy = OrderPileOccupy.builder()
                .status(OccupyOrderStatusEnum.ORDER_HANG_UP.getCode()) // 2-订单挂起
                .plateNumber(plateNumber)
                .build();
        List<OrderPileOccupy> occupyList = orderPileOccupyService.selectOrderPileOccupyList(occupy);
        // todo 如果有占桩订单，则先提醒“需支付占桩订单”
        if (CollectionUtils.isNotEmpty(occupyList)) {
            return "需支付占桩订单";
        }
        // 根据车牌号找出绑定小程序的用户
        List<MemberVO> memberList = memberBasicInfoService.getMemberInfoByPlateNumber(plateNumber);
        GenerateOccupyOrderDTO dto = new GenerateOccupyOrderDTO();
        dto.setPileSn(devName); // TODO 设备名称
        dto.setConnectorCode(String.valueOf(zoneId)); // TODO 车位id
        dto.setOrderStatus(OccupyOrderStatusEnum.ORDER_HANG_UP.getCode()); // 订单挂起
        dto.setPayStatus(Constants.ZERO); // 未支付
        String occupyPileOrderCode = null;
        if (CollectionUtils.isNotEmpty(memberList)) {
            // 如果是有小程序的用户，则先降地锁，然后生成一笔占桩订单
            // 发送降锁指令
            Object cacheObject = sendGroundLockMsg(sn, Integer.parseInt(zoneId));
            if (cacheObject != null) {
                // 降锁成功，生成占桩订单(挂起、未支付)
                dto.setMemberId(memberList.get(0).getMemberId());
                occupyPileOrderCode = orderPileOccupyService.generateOccupyPileOrder(dto);
            }
        }else {
            // 如果没有小程序账号，再根据此车牌是否有挂起的占桩订单
            OrderPileOccupy orderPileOccupy = OrderPileOccupy.builder()
                    .plateNumber(plateNumber)
                    .build();
            List<OrderPileOccupy> orderPileOccupyList = orderPileOccupyService.selectOrderPileOccupyList(orderPileOccupy);
            // TODO 如果有已挂起的占桩订单，则不予降锁，将“已存在有未支付的占桩订单”信息返回
            if (CollectionUtils.isNotEmpty(orderPileOccupyList)) {
                return "已存在有未支付的占桩订单";
            }
            // 如果没有，则先降锁，再生成一笔占桩订单 (与车牌号绑定)
            Object cacheObject = sendGroundLockMsg(sn, Integer.parseInt(zoneId));
            if (cacheObject == null) {
                return null;
            }
            dto.setPlateNumber(plateNumber);
            occupyPileOrderCode = orderPileOccupyService.generateOccupyPileOrder(dto);
        }
        return occupyPileOrderCode;
    }

    /**
     * 对数据进行解析并存储到数据库
     * @param jsonObject
     * @return
     */
    private Map<String, String> saveInfo2DataBase(JSONObject jsonObject) {
        Map<String, String> resultMap = new LinkedHashMap<>();
        // 解析 jsonObject
        // 车牌信息
        CameraIdentifyResultsDTO.ProductH.Plate plate = JSONObject.parseObject(jsonObject.getJSONObject("product_h")
                        .getJSONObject("plate").toJSONString(),
                CameraIdentifyResultsDTO.ProductH.Plate.class);
        if (plate == null) {
            logger.error("车位相机解析 error, 车牌信息为空");
            return null;
        }
        // 设备信息
        CameraIdentifyResultsDTO.DeviceInfo deviceInfo = JSONObject.parseObject(jsonObject.getJSONObject("device_info").toJSONString(),
                CameraIdentifyResultsDTO.DeviceInfo.class);
        if (deviceInfo == null) {
            logger.error("车位相机解析 error, 设备信息为空");
            return null;
        }
        // 停车位信息
        CameraIdentifyResultsDTO.ProductH.Parking parking = JSONObject.parseObject(jsonObject.getJSONObject("product_h")
                        .getJSONObject("parking").toJSONString(),
                CameraIdentifyResultsDTO.ProductH.Parking.class);
        if (parking == null) {
            logger.error("车位相机解析 error, 停车位信息为空");
            return null;
        }
        // 获取背景图片
        JSONArray bgImgs = jsonObject.getJSONArray("bg_img");
        List<CameraIdentifyResultsDTO.BgImg> bgImgList = bgImgs.toList(CameraIdentifyResultsDTO.BgImg.class);
        if (CollectionUtils.isEmpty(bgImgList)) {
            logger.error("车位相机解析 error, 背景图片信息为空");
            return null;
        }
        String sn = deviceInfo.getSn();
        Integer zoneId = parking.getZoneId();
        String devName = deviceInfo.getDevName();
        // Base64 解密
        String plateNumber = cn.hutool.core.codec.Base64.decodeStr(plate.getPlate());
        String zoneName = cn.hutool.core.codec.Base64.decodeStr(parking.getZoneName());
        // 将解密后的值重新 set 进对象中，便于存储数据库
        plate.setPlate(plateNumber);
        parking.setZoneName(zoneName);

        // 循环 bgImgList, 将图片分成单张进行存储
        for (CameraIdentifyResultsDTO.BgImg bgImg : bgImgList) {
            // 上传到阿里云OSS，获取图片上传成功后的地址
            String fileName = zoneName + "-" + System.currentTimeMillis() / 1000 + ".jpg";
            String url = saveImage(bgImg.getImage(), fileName);
            if (StringUtils.isBlank(url)) {
                logger.error("车位号：{} 图片上传失败", zoneName);
                continue;
            }
            PileCameraInfo pileCameraInfo = PileCameraInfo.builder()
                    .deviceName(devName)
                    .deviceIp(deviceInfo.getIp())
                    .deviceSn(sn)
                    .plateNumber(plateNumber)
                    .parkingState(String.valueOf(parking.getParkingState()))
                    .zoneId(zoneId)
                    .zoneName(zoneName)
                    .color(plate.getColor())
                    .plateType(plate.getType())
                    .image(url)

                    .build();
            // 插入数据库
            pileCameraInfoService.insertPileCameraInfo(pileCameraInfo);
        }
        // 构建返回参数
        resultMap.put("sn", sn);
        resultMap.put("zoneId", String.valueOf(zoneId));
        resultMap.put("plateNumber", plateNumber);
        resultMap.put("devName", devName);
        return resultMap;
    }

    /**
     * 保存图像
     * @param base64Image 图像的Base64编码
     * @param fileName    文件名
     * @return
     */
    private String saveImage(String base64Image, String fileName) {
        try {
            // 将Base64编码的字符串解码为字节数组
            byte[] imageBytes = Base64.getDecoder().decode(base64Image);

            // 将字节数组转换为 BufferedImage
            ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes);
            BufferedImage originalImage = ImageIO.read(inputStream);

            // 对图像进行压缩
            double quality = 0.5; // 压缩质量
            BufferedImage compressedImage = ImageUtils.compressImage(originalImage, quality);

            // 将压缩后的图片转换为字节数组
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ImageIO.write(compressedImage, "jpg", outputStream);
            byte[] compressedImageBytes = outputStream.toByteArray();

            // 上传图片到OSS
            String url = AliyunOssUploadUtils.upload2OSS(compressedImageBytes, fileName);
            if (StringUtils.isNotBlank(url)) {
                return url;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 发送地锁升降指令
     * @param sn 相机的sn号
     * @param zoneId 相机对应的车位id
     * @return 发送降锁指令的响应对象
     * @throws InterruptedException
     */
    private Object sendGroundLockMsg(String sn, Integer zoneId) throws InterruptedException {
        Camera2GroundLockCommand command = Camera2GroundLockCommand.builder()
                .sn(sn)
                .msgType("GroundlockOption")
                .msgPrefix("GO")
                .topic("/remoteCommand")
                .zoneId(zoneId)
                .option(3) // 3-降锁后不自动升锁
                .force(0) // 强制操作 0：否 1：是

                .build();
        JSONObject msgData = new JSONObject();
        msgData.put("option", command.getOption());
        msgData.put("zone_id", command.getZoneId());
        msgData.put("force", command.getForce());

        String msgId = sendMsg2Topic(command.getSn(), command.getMsgType(), command.getMsgPrefix(), command.getTopic(), msgData);

        // 判断降锁是否成功
        // 将此降锁指令存入缓存, 在 nettyServer 收到新消息时判断是否是此消息的回复
        String redisKey = CacheConstants.SEND_DROP_LOCK_COMMOND + msgId;
        redisCache.setCacheObject(redisKey, command, 5, TimeUnit.MINUTES);

        // 延时 3 秒钟, 再查询缓存中是否有此降锁信息的回复
        Thread.sleep(3);

        String responseRedisKey = CacheConstants.GROUND_LOCK_DEVICE_REPORT + msgId;
        Object cacheObject = redisCache.getCacheObject(responseRedisKey);
        return cacheObject;
    }


    /**
     * 发送具体指令到某主题
     * @param sn 设备 sn
     * @param msgType 消息类型
     * @param msgPrefix 消息前缀
     * @param topic 主题
     * @param msgData 消息内容
     *
     * @throws InterruptedException
     * @return msg_id 相当于此消息的唯一标识，用于辨识返回报文
     */
    public String sendMsg2Topic(String sn, String msgType, String msgPrefix, String topic, JSONObject msgData) throws InterruptedException {
        JSONObject jsonObject = spliceStr(sn, msgType, msgPrefix);
        // 通过sn查找出对应的channelId
        String mqttConnectRedisKey = CacheConstants.MQTT_CONNECT_SN + sn;
        Object cacheObject = redisCache.getCacheObject(mqttConnectRedisKey);
        if (cacheObject == null) {
            return null;
        }
        String channelId = (String) cacheObject;
        if (msgData != null) {
            jsonObject.put("msg_data", msgData);
        }

        logger.info("给相机发送远程命令，sn:{}, 消息类型:{}, 主题:{}, 最终发送数据:{}", sn, msgType, topic, jsonObject.toJSONString());
        // 发送消息
        handler.sendMsg(channelId, topic, jsonObject.toJSONString());
        return jsonObject.getString("msg_id");
    }

    /**
     * 根据规则拼装字符串
     * @param sn 设备 sn
     * @param msgType 消息类型
     * @param msgPrefix 消息前缀
     * @return 拼装好的json对象
     */
    private JSONObject spliceStr(String sn, String msgType, String msgPrefix) {
        StringBuilder sb = new StringBuilder();
        String msgId = msgPrefix + DateUtils.dateTimeNow(DateUtils.YYYYMMDDHHMMSS) + "01";
        String timeStamp = DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD_HH_MM_SS);
        sb.append("sn=").append(sn)
                .append("&timestamp=").append(timeStamp)
                .append("&msg_id=").append(msgId)
                .append("&msg_type=").append(msgType);
        // 进行 32 位 MD5 计算
        String sign = MD5Util.MD5Encode(sb.toString()).toUpperCase(Locale.ROOT);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("sign", sign);
        jsonObject.put("sn", sn);
        jsonObject.put("timestamp", timeStamp);
        jsonObject.put("msg_id", msgId);
        jsonObject.put("msg_type", msgType);

        return jsonObject;
    }


    /**
     * 将车辆图片信息存入缓存
     * @param jsonObject
     */
    // private boolean saveCarPicture2Redis(JSONObject jsonObject, String parkingState) {
    //     // 获取车牌号
    //     String plateNumber = jsonObject.getJSONObject("plate").getString("plate");
    //     if (StringUtils.isBlank(plateNumber)) {
    //         return false;
    //     }
    //     // 获取背景图片
    //     JSONArray bgImgs = jsonObject.getJSONArray("bg_img");
    //     List<CameraIdentifyResultsDTO.BgImg> bgImgList = bgImgs.toList(CameraIdentifyResultsDTO.BgImg.class);
    //     if (CollectionUtils.isEmpty(bgImgList)) {
    //         return false;
    //     }
    //     for (CameraIdentifyResultsDTO.BgImg bgImg : bgImgList) {
    //         String image = bgImg.getImage(); // 图片的 base64 编码
    //         // String key = bgImg.getKey(); // 索引id
    //         // key: 前缀 + 车牌号 + 日期 + 入场/出场状态
    //         String redisKey = CacheConstants.CAMERA_IMAGE_BY_PLATE_NUMBER + plateNumber + "_" + DateUtils.getDate() + "_" + parkingState;
    //         // 存入缓存
    //         // 暂时永久保存
    //         redisCache.setCacheObject(redisKey, image);
    //     }
    //     return true;
    // }

}
