package com.sz.admin.monitor.service.impl;

import cn.hutool.core.lang.UUID;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.sz.admin.monitor.enums.HCPlayControlEnum;
import com.sz.admin.monitor.mapper.AlgorithmTaskMapper;
import com.sz.admin.monitor.mapper.CameraMapper;
import com.sz.admin.monitor.mapper.EdgeBoxMapper;
import com.sz.admin.monitor.mapper.NvrMapper;
import com.sz.admin.monitor.pojo.dto.Ipchannel.ControlDTO;
import com.sz.admin.monitor.pojo.dto.edgebox.AlgorithmTaskDTO;
import com.sz.admin.monitor.pojo.po.AlgorithmTask;
import com.sz.admin.monitor.pojo.po.Camera;
import com.sz.admin.monitor.pojo.po.EdgeBox;
import com.sz.admin.monitor.pojo.po.Nvr;
import com.sz.admin.monitor.pojo.vo.camerasnapshot.CameraSnapshotVO;
import com.sz.admin.monitor.pojo.vo.ptz.PTZVO;
import com.sz.admin.monitor.pojo.vo.watchful.WatchfulVO;
import com.sz.admin.monitor.sdk.ManageNVR;
import com.sz.admin.monitor.service.CameraSnapshotService;
import com.sz.admin.monitor.service.IpChannelService;
import com.sz.admin.monitor.service.PresetService;
import com.sz.admin.monitor.utils.AiBoxRequestUtil;
import com.sz.admin.monitor.utils.AlgMediaConfigResponse;
import com.sz.admin.monitor.utils.RtspUtil;
import com.sz.admin.monitor.utils.ZLMediaKitUtils;
import com.sz.core.common.entity.TransferMessage;
import com.sz.core.common.exception.common.BusinessException;
import com.sz.core.util.BeanCopyUtils;
import com.sz.core.util.JsonUtils;
import com.sz.platform.enums.AdminResponseEnum;
import com.sz.redis.handler.WsToServiceMsgHandler;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * ClassName: IpChannelServiceImpl
 * Package: com.sz.admin.monitor.service.impl
 * Description:
 */
@Service
@Slf4j
public class IpChannelServiceImpl extends ServiceImpl<CameraMapper, Camera> implements IpChannelService, WsToServiceMsgHandler {
    @Resource
    private CameraMapper cameraMapper;
    @Resource
    private NvrMapper nvrMapper;
    @Resource
    private ManageNVR manageNVR;
    @Resource
    private ZLMediaKitUtils zlMediaKitUtils;

    @Resource
    private AlgorithmTaskMapper algorithmTaskMapper;

    @Resource
    private PresetService presetService;

    @Resource
    private AiBoxRequestUtil aiBoxRequestUtil;

    @Resource
    private CameraSnapshotService cameraSnapshotService;

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private EdgeBoxMapper edgeBoxMapper;

    // Redis Key 前缀
    private static final String VIEWERS_KEY_PREFIX = "camera:viewers:";
    // 所有的活跃流Key集合
    private static final String ACTIVE_CHANNELS_SET = "camera:active_channels";


    @Override
    public Camera getByChannelId(Integer channelId) {
        QueryWrapper wrapper = QueryWrapper.create()
            .select()
            .from(Camera.class)
            .eq(Camera::getChannelId, channelId);
        return  cameraMapper.selectOneByQuery(wrapper);
    }

    @Override
    public void userHeartbeat(Long id,String uuid) {
        String key = VIEWERS_KEY_PREFIX + id;
        long now = System.currentTimeMillis();
        // 在zSet中更新用户的时间戳
        stringRedisTemplate.opsForZSet().add(key, uuid, now);
        // 设置key的过期时间为一个小时
        stringRedisTemplate.expire(key, 1, TimeUnit.HOURS);
        // 将该频道加入到活跃频道列表，给定时任务进行遍历
        stringRedisTemplate.opsForSet().add(ACTIVE_CHANNELS_SET, String.valueOf(id));
    }

    /**
     *
     * @param id 通道id
     * @return
     */
    @Override
    public String preview(Long id)  {
        Camera camera = this.getById(id);
        if(camera.getStatus()==0)
        {
            // 如果摄像头不在线，直接返回
            throw new BusinessException(AdminResponseEnum.OPERATION_FAIL,null,"摄像头不在线");
        }
        Nvr nvr = nvrMapper.selectOneById(camera.getNvrId());
        Map<String, String> rtspMap = RtspUtil.getRtsp(nvr.getDriver(), nvr.getAccount(), nvr.getPassword(), camera.getChannelId()-32, nvr.getIp(), 554);
        // 获取主码流
        String mainRtsp = rtspMap.get("mainRtsp");
        log.info("主码流的rtsp信息: {}", mainRtsp);
        // 进行拉流代理
        JSONObject jsonObject =zlMediaKitUtils.addStreamProxy(String.valueOf(id), mainRtsp);
        // JSONObject jsonObject = zlMediaKitUtils.addStreamProxy("test", "rtsp://132.239.12.145:554/axis-media/media.amp");
        if ("0".equals(jsonObject.get("code").toString())) {
            //拉流代理成功
            return zlMediaKitUtils.getPlayUrl(String.valueOf(id));
        } else if (jsonObject.get("msg").toString().contains("already exists") || "-1".equals(jsonObject.get("code").toString())) {
            // 流已经存在了，直接复用
            return zlMediaKitUtils.getPlayUrl(String.valueOf(id));
        } else {
            throw new BusinessException(AdminResponseEnum.DEVICE_ADD_PROXY_FAIL,null,"添加流代理失败");
        }
    }

    // 停止预览
    @Override
    public Integer stopPreview(Long id, String uuid) {
        String key = VIEWERS_KEY_PREFIX + id;
        // 从zSet中移除该用户
        stringRedisTemplate.opsForZSet().remove(key, uuid);
        return checkAndCloseStream(id);
    }

    /**
     * 定时任务：看门狗 (每 10 秒执行一次)
     */
    @Scheduled(fixedRate = 10000)
    public void scanZombieStreams() {
        // 获取所有记录在案的活跃频道
        Set<String> channels = stringRedisTemplate.opsForSet().members(ACTIVE_CHANNELS_SET);
        if (channels == null || channels.isEmpty()) return;

        for (String channelIdStr : channels) {
            Long id = Long.parseLong(channelIdStr);
            checkAndCloseStream(id);
        }
    }

    // 检查流是否应该关闭
    private Integer checkAndCloseStream(Long id) {
        String key = VIEWERS_KEY_PREFIX + id;
        long now = System.currentTimeMillis();
        // 阈值：30秒没心跳视为掉线
        long threshold = now - 30000;
        // 移除超过30秒没有更新心跳的成员
        stringRedisTemplate.opsForZSet().removeRangeByScore(key, 0, threshold);
        // 统计剩余人数
        Long count = stringRedisTemplate.opsForZSet().zCard(key);
        if (count == null || count == 0) {
            // 如果剩余人数为0了，那么意味着这个摄像头的流没有人在观看了直接关流
            JSONObject jsonObject = zlMediaKitUtils.CloseStreamProxy(String.valueOf(id));
            log.info("关闭流：{}", jsonObject.toString());
            // 清理redis中的数据
            stringRedisTemplate.delete(key);
            stringRedisTemplate.opsForSet().remove(ACTIVE_CHANNELS_SET, String.valueOf(id));
            return 1;
        } else {
            log.info("摄像头[{}] 当前在线观看人数：{}", id, count);
            return -1;
        }
    }


    @Override
    public Object ptzControl(Long id, Integer direction, int speed, int startFlag) {
        HCPlayControlEnum hcPlayControlEnum = HCPlayControlEnum.getByCode(direction);
        if (hcPlayControlEnum == null) {
            throw new BusinessException(AdminResponseEnum.DEVICE_PTZ_CONTROL_FAIL, null, "PTZ控制失败");
        }
        return manageNVR.ptzCon(id.intValue(), hcPlayControlEnum.getCode().toString(), String.valueOf(speed), String.valueOf(startFlag));
    }

    @Override
    public Object controlPreset(Long id, int ptzPresetCmd, int dwPresetIndex, String pointName, int isBasicPoint) {
        if (ptzPresetCmd == 8 && isBasicPoint == 1) {
            // 如果是设置预置位并且是基准位，那么就需要抓取一张标准图片
            CameraSnapshotVO cameraSnapshotVO = cameraSnapshotService.captureAndSave(id, 1, dwPresetIndex);
            Camera camera = this.getById(id);
            // 保存基准图
            camera.setHomeImagePath(cameraSnapshotVO.getImagePath());
            updateById(camera);
        }
        return manageNVR.ptzPresets(id.intValue(), ptzPresetCmd, String.valueOf(dwPresetIndex), pointName);
    }

    // 开启一个定时任务，去校验摄像头的ptz参数是否一致了，如果一致则是归位了
    // @Scheduled(fixedRate = 1000) // 每秒执行一次
    public void scanSentinelTasks() {
        String s = stringRedisTemplate.opsForValue().get("camera:sentinel");
        PTZVO ptzvo = JSON.parseObject(s, PTZVO.class);
        if (ptzvo != null) {
            // 获取当前的位置
            PTZVO curPtz = manageNVR.getPTZLocation(ptzvo.getCameraId());
            curPtz.setCameraId(ptzvo.getCameraId());
            log.info("当前位置：{}", curPtz);
            if (ptzvo.equals(curPtz)) {
                // 如果两个位置相同，则是归位了 TODO 进行图片对比的功能
                log.info("redis中的ptz信息：{}，当前位置的ptz信息：{}", ptzvo, curPtz);
            }
        }

    }

    // 开启和关闭云台守望
    @Override
    @Transactional
    public boolean ControlSentinel(Long id, int openOrNo, int watchTime, Integer presetIndex) {
        boolean flag=false;
        if (openOrNo==0)
        {
             flag = manageNVR.watchful(id.intValue(), openOrNo, watchTime, -1);
        }else {
             flag = manageNVR.watchful(id.intValue(), openOrNo, watchTime, presetIndex);
        }
        if (flag) {
            // 获取当前预置点的ptz坐标，并且保存
            PTZVO ptzLocation = manageNVR.getPTZLocation(id);
            ptzLocation.setCameraId(id);
            // 将ptz保存到redis中
            stringRedisTemplate.opsForValue().set("camera:sentinel", JSON.toJSONString(ptzLocation));
            log.info("ptz 坐标：{}", ptzLocation);
            Camera camera = this.getById(id);
            // 保存守望信息
            camera.setWatchfulTime(watchTime);
            camera.setWatchfulIndex(presetIndex);
            if (openOrNo != 0) {
                cameraMapper.update(camera);
            }

        }
        return flag;
    }

    @Override
    public WatchfulVO getWatchful(Long id) {
        Camera camera = this.getById(id);
        //  WatchfulVO watchful = manageNVR.getWatchful(channelId);
        return BeanCopyUtils.copy(camera, WatchfulVO.class);
    }

    @Override
    public AlgMediaConfigResponse configAlg(AlgorithmTaskDTO algorithmTaskDTO) {
        Long cameraId = algorithmTaskDTO.getCameraId();
        // 查询摄像机
        Camera camera = cameraMapper.selectOneById(cameraId);
        if (camera==null)
        {
            throw new BusinessException(AdminResponseEnum.OPERATION_FAIL, null, "摄像头不存在");
        }
        Long boxId = camera.getBoxId();
        if (boxId == null) {
            throw new BusinessException(AdminResponseEnum.OPERATION_FAIL, null, "摄像头没有绑定盒子");
        }
        EdgeBox edgeBox = edgeBoxMapper.selectOneById(boxId);
        List<Integer> algInfo = algorithmTaskDTO.getAlgInfo();
        if (algInfo == null)
        {
            algInfo = new ArrayList<>();
        }
        if (algInfo.size()>3)
        {
            throw new BusinessException(AdminResponseEnum.OPERATION_FAIL, null, "算法配置同时最多支持3个");
        }
        // 摄像机名称，从数据库查询获取
        algorithmTaskDTO.setMediaName(camera.getId().toString());
        // 查询该摄像机是否已有算法配置记录
        QueryWrapper queryWrapper = QueryWrapper.create()
            .select()
            .from(AlgorithmTask.class)
            .where(AlgorithmTask::getCameraId).eq(cameraId)
            .orderBy(AlgorithmTask::getCreateTime, false)
            .limit(1);
        AlgorithmTask existingTask = algorithmTaskMapper.selectOneByQuery(queryWrapper);
        String sessionUuid;
        LocalDateTime createTime=null;
        if (existingTask != null)
        {
            // 修改
            sessionUuid = existingTask.getAlgTaskSession();
            createTime=existingTask.getCreateTime();
        }else {
            // 新增
            sessionUuid=UUID.randomUUID().toString().replaceAll("-", "");
        }
        algorithmTaskDTO.setAlgTaskSession(sessionUuid);
        List<Integer> algList= new ArrayList<>();
        if (algInfo.contains(8)) {
            // 配置子算法
            algList.add(46);
        }
        if(algInfo.contains(52))
        {
            algList.add(207);
        }
        if(algInfo.contains(58))
        {
           algList.add(248);
        }
        if (algInfo.contains(1)) {
            algList.add(195);
        }
        AlgorithmTaskDTO.UserDataDto userDataDto = AlgorithmTaskDTO.UserDataDto.builder()
            .build();
        if (algorithmTaskDTO.getUserData()!=null)
        {
            BeanCopyUtils.copy(algorithmTaskDTO.getUserData(), userDataDto);
        }
        userDataDto.setMethodConfig(algList);
        algorithmTaskDTO.setUserData(userDataDto);
        // DTO转PO
        AlgorithmTask algorithmTask = BeanCopyUtils.copy(algorithmTaskDTO, AlgorithmTask.class);
        algorithmTask.setIsDeleted("F");
        algorithmTask.setCreateTime(createTime);
        ObjectMapper mapper = new ObjectMapper();
        try {
            String userDataJson = mapper.writeValueAsString(algorithmTaskDTO.getUserData());
            JsonNode jsonNode = mapper.readTree(userDataJson);
            algorithmTask.setUserData(jsonNode);
        }catch (Exception e)
        {
            throw new RuntimeException("UserData转换失败", e);
        }
        AlgMediaConfigResponse response;
        try {
            if (existingTask != null) {
                log.info("摄像机已存在配置，先清理旧任务: {}", sessionUuid);
                aiBoxRequestUtil.controlTask(edgeBox.getIp(), edgeBox.getPort(), sessionUuid, 0);
                aiBoxRequestUtil.deleteTask(edgeBox.getIp(), edgeBox.getPort(), sessionUuid);
            }
            // 应用新算法
            response = aiBoxRequestUtil.setupTask(edgeBox.getIp(), edgeBox.getPort(), algorithmTaskDTO);
        } catch (Exception e) {
            log.error("AI 盒子任务下发失败, 摄像机: {}", camera.getName(), e);
            throw new RuntimeException("下发算法配置到设备失败，请检查网络或设备状态", e);
        }
        try {
            algorithmTaskMapper.insertOrUpdate(algorithmTask);
        } catch (Exception e) {
            log.error("本地数据库保存算法配置失败，开始尝试回滚盒子端任务。摄像机: {}", camera.getName(), e);
            try {
                // 把刚才成功创建的盒子任务删掉
                aiBoxRequestUtil.controlTask(edgeBox.getIp(), edgeBox.getPort(), sessionUuid, 0);
                aiBoxRequestUtil.deleteTask(edgeBox.getIp(), edgeBox.getPort(), sessionUuid);
            } catch (Exception rollbackEx) {
                log.error("【严重】数据库落库失败，且清理AI盒子配置也失败！", rollbackEx);
            }
            throw new RuntimeException("系统内部异常，算法配置数据保存失败");
        }
        return response;
    }
    // 在这里接收透传过来的 websocket信息，进行业务处理
    @Override
    public void handlerMsg(TransferMessage transferMessage) {
        Object data = transferMessage.getMessage().getData();
        ControlDTO controlDTO = JsonUtils.parseObject(JsonUtils.toJsonString(data), ControlDTO.class);
        if (controlDTO != null)
        {
            this.ptzControl(controlDTO.getId(), controlDTO.getDirection(), controlDTO.getSpeed(), controlDTO.getStartFlag());
        }
    }
}
