package cn.skcks.docking.gb28181.wvp.service.gb28181;

import cn.skcks.docking.gb28181.common.json.JsonUtils;
import cn.skcks.docking.gb28181.common.redis.RedisUtil;
import cn.skcks.docking.gb28181.core.sip.gb28181.cache.CacheUtil;

import cn.skcks.docking.gb28181.core.sip.utils.SipUtil;
import cn.skcks.docking.gb28181.media.dto.rtp.CloseRtpServer;
import cn.skcks.docking.gb28181.media.proxy.ZlmMediaService;
import cn.skcks.docking.gb28181.sdp.GB28181SDPBuilder;
import cn.skcks.docking.gb28181.wvp.orm.mybatis.dynamic.model.WvpProxyDevice;
import cn.skcks.docking.gb28181.wvp.orm.mybatis.dynamic.model.WvpProxyDocking;
import cn.skcks.docking.gb28181.wvp.service.device.DeviceService;
import cn.skcks.docking.gb28181.wvp.service.docking.DockingService;
import cn.skcks.docking.gb28181.wvp.sip.request.SipRequestBuilder;
import cn.skcks.docking.gb28181.wvp.sip.sender.SipSender;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.concurrent.*;

@Slf4j
@Service
@RequiredArgsConstructor
public class RealtimeManager {
    public final static String REALTIME_KEY = "realtime:";
    private final ZlmMediaService zlmMediaService;
    private final DeviceService deviceService;
    private final DockingService dockingService;
    private final SipSender sender;
    private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
    private final ConcurrentMap<String, ScheduledFuture<?>> playing = new ConcurrentHashMap<>();

    private String getRealtimeKey(String deviceCode){
        return REALTIME_KEY + deviceCode;
    }

    public void addPlaying(String deviceCode, Gb28181DownloadService.VideoInfo videoInfo) {
        RedisUtil.StringOps.set(CacheUtil.getKey(GB28181SDPBuilder.Action.PLAY.getAction(), deviceCode), videoInfo.getUrl());

        ScheduledFuture<?> schedule = playing.get(deviceCode);
        if(schedule != null){
            schedule.cancel(true);
        }

        // 定时任务
        schedule = scheduledExecutorService.schedule(()->{
            log.info("[定时任务] 关闭设备(deviceCode => {}) 视频", deviceCode);
            playing.remove(deviceCode);
            Gb28181DownloadService.VideoInfo _videoInfo = JsonUtils.parse(RedisUtil.StringOps.get(getRealtimeKey(deviceCode)), Gb28181DownloadService.VideoInfo.class);
            RedisUtil.KeyOps.delete(getRealtimeKey(deviceCode));
            close(deviceCode, _videoInfo);
        }, 3, TimeUnit.MINUTES);

        // 缓存
        RedisUtil.StringOps.set(getRealtimeKey(deviceCode), JsonUtils.toJson(videoInfo));
        playing.put(deviceCode, schedule);
    }

    public void removePlaying(String deviceCode) {
        ScheduledFuture<?> schedule = playing.get(deviceCode);
        if(schedule != null){
            schedule.cancel(true);
            playing.remove(deviceCode);
        }

        Gb28181DownloadService.VideoInfo videoInfo = JsonUtils.parse(RedisUtil.StringOps.get(getRealtimeKey(deviceCode)), Gb28181DownloadService.VideoInfo.class);
        if(videoInfo == null) {
            log.warn("未找到 设备(deviceCode => {}) 视频信息", deviceCode);
            return;
        }

        close(deviceCode, videoInfo);
    }

    /**
     * 关闭视频连接
     * @param deviceCode 设备编码
     * @param videoInfo 视频信息
     */
    private void close(String deviceCode, Gb28181DownloadService.VideoInfo videoInfo){
        RedisUtil.KeyOps.delete(getRealtimeKey(deviceCode));
        RedisUtil.KeyOps.delete(CacheUtil.getKey(GB28181SDPBuilder.Action.PLAY.getAction(), deviceCode));

        WvpProxyDevice device = deviceService.getDeviceByDeviceCode(deviceCode).orElse(null);
        if(device == null){
            return;
        }
        WvpProxyDocking docking = dockingService.getDeviceByDeviceCode(device.getGbDeviceId()).orElse(null);
        if(docking == null){
            return;
        }

        String cacheKey = CacheUtil.getKey(docking.getGbDeviceId(), device.getGbDeviceChannelId());
        String existCallId = RedisUtil.StringOps.get(cacheKey);

        String deviceIp = docking.getIp();
        int devicePort = Integer.parseInt(docking.getPort());

        // 判断缓存的 视频信息 与 缓存的 callId 是否相同 避免 下级点播出现信息不一致
        if(videoInfo.getCallId().equalsIgnoreCase(existCallId)){
            log.info("关闭视频连接, {} => {}",deviceCode, existCallId);
            sender.sendRequest((provider, localIp, localPort) ->
                    SipRequestBuilder.createByeRequest(deviceIp, devicePort, device.getGbDeviceChannelId(), SipUtil.generateFromTag(), null, existCallId));
        }

        // 无论是否存在都调用一次 zlm 关闭流, 避免异常情况
        zlmMediaService.closeRtpServer(CloseRtpServer.builder()
                .streamId(videoInfo.getStreamId())
                .build());
    }
}
