package com.usky.cdi.service.impl;

import com.alibaba.fastjson.JSON;
import com.usky.cdi.service.CdiDeliveryLogService;
import com.usky.cdi.service.config.Gb28181VideoProperties;
import com.usky.cdi.service.enums.MqttTopics;
import com.usky.cdi.service.mqtt.MqttConnectionTool;
import com.usky.cdi.service.util.Gb28181MqttCredentialResolver;
import com.usky.cdi.service.util.Gb28181VideoPlatformClient;
import com.usky.cdi.service.util.SnowflakeIdGenerator;
import com.usky.cdi.service.vo.video.VideoChannelVO;
import com.usky.cdi.service.vo.video.VideoDeviceItemVO;
import com.usky.cdi.service.vo.video.VideoPlayInfoVO;
import com.usky.cdi.service.vo.video.VideoStreamItemVO;
import com.usky.cdi.service.vo.video.VideoStreamSyncPacketVO;
import com.usky.common.core.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * WVP 点播推流：调用平台 API 拉流，并通过 MQTT 上报播放地址。
 */
@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "gb28181.video", name = "enabled", havingValue = "true")
public class VideoStreamPushService {

    private static final int DELIVERY_DATA_TYPE = 6;

    @Value("${snowflake.worker-id:5}")
    private long workerId;

    @Value("${snowflake.data-center-id:5}")
    private long dataCenterId;

    private final Gb28181VideoProperties properties;
    private final Gb28181VideoPlatformClient platformClient;
    private final MqttConnectionTool mqttConnectionTool;
    private final CdiDeliveryLogService cdiDeliveryLogService;
    private final Gb28181MqttCredentialResolver mqttCredentialResolver;

    private final Map<String, VideoPlayInfoVO> activeSessions = new ConcurrentHashMap<>();
    private SnowflakeIdGenerator idGenerator;
    private final SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

    @PostConstruct
    public void init() {
        this.idGenerator = new SnowflakeIdGenerator(workerId, dataCenterId);
    }

    public VideoPlayInfoVO pushStream(String deviceId, String channelId) {
        validateIds(deviceId, channelId);
        VideoPlayInfoVO playInfo = platformClient.startPlay(deviceId, channelId);
        activeSessions.put(sessionKey(deviceId, channelId), playInfo);
        log.info("WVP 点播成功: deviceId={}, channelId={}, url={}",
                deviceId, channelId, playInfo.getPreferredPlayUrl(properties.getPreferredProtocol()));
        return playInfo;
    }

    public VideoStreamSyncPacketVO pushAllOnlineStreams() {
        Gb28181MqttCredentialResolver.MqttCredentials credentials = mqttCredentialResolver.resolve();
        long startTime = System.currentTimeMillis();
        List<VideoStreamItemVO> streamItems = new ArrayList<>();
        int total = 0;
        int failure = 0;

        List<VideoDeviceItemVO> devices = platformClient.queryAllDevicesWithChannels();
        int max = Math.max(1, properties.getMaxConcurrentStreams());
        int pushed = 0;

        for (VideoDeviceItemVO device : devices) {
            if (!"ON".equalsIgnoreCase(device.getStatus()) || device.getChannels() == null) {
                continue;
            }
            for (VideoChannelVO channel : device.getChannels()) {
                if (!"ON".equalsIgnoreCase(channel.getStatus()) || pushed >= max) {
                    continue;
                }
                total++;
                try {
                    VideoPlayInfoVO playInfo = pushStream(device.getDeviceId(), channel.getChannelId());
                    streamItems.add(toStreamItem(device, channel, playInfo));
                    pushed++;
                } catch (Exception e) {
                    failure++;
                    log.warn("点播失败 deviceId={} channelId={}: {}",
                            device.getDeviceId(), channel.getChannelId(), e.getMessage());
                    streamItems.add(toFailedStreamItem(device, channel, e.getMessage()));
                }
            }
        }

        VideoStreamSyncPacketVO packet = buildStreamSyncPacket(credentials.getEngineeringId(), streamItems);
        publishStreamPacket(credentials, packet, startTime, total, failure);
        return packet;
    }

    public void maintainActiveStreams() {
        if (!properties.isStreamPushEnabled()) {
            return;
        }
        if (properties.isAutoPushAllOnline()) {
            pushAllOnlineStreams();
            return;
        }
        log.debug("当前 WVP 活跃推流会话数: {}", activeSessions.size());
    }

    public void stopStream(String deviceId, String channelId) {
        validateIds(deviceId, channelId);
        platformClient.stopPlay(deviceId, channelId);
        VideoPlayInfoVO removed = activeSessions.remove(sessionKey(deviceId, channelId));
        if (removed != null) {
            removed.setStreamStatus("STOPPED");
        }
        log.info("已停止 WVP 点播: deviceId={}, channelId={}", deviceId, channelId);
    }

    public Map<String, VideoPlayInfoVO> getActiveSessions() {
        return Collections.unmodifiableMap(new LinkedHashMap<>(activeSessions));
    }

    private void publishStreamPacket(Gb28181MqttCredentialResolver.MqttCredentials credentials,
                                     VideoStreamSyncPacketVO packet, long startTime,
                                     int total, int failure) {
        String topic = resolveStreamTopic();
        String jsonMessage = JSON.toJSONString(packet);
        try {
            MqttConnectionTool.MqttGateway gateway = mqttConnectionTool.connectOrRefresh(
                    credentials.getUsername(), credentials.getPassword());
            gateway.sendToMqtt(topic, jsonMessage);
            log.info("视频流 MQTT 上报成功: topic={}, streams={}", topic, packet.getStreams().size());
        } catch (Exception e) {
            failure = total;
            log.error("视频流 MQTT 上报失败: {}", e.getMessage(), e);
            throw new BusinessException("视频流 MQTT 上报失败: " + e.getMessage());
        } finally {
            long endTime = System.currentTimeMillis();
            int success = total - failure;
            cdiDeliveryLogService.saveLog(topic, MqttTopics.IotInfo.VIDEO_STREAM.getDesc(), DELIVERY_DATA_TYPE,
                    credentials.getTenantId(), credentials.getEngineeringId(), LocalDateTime.now(),
                    startTime, endTime, total, success, failure, 0, failure > 0 ? 0 : 1, "自动同步");
        }
    }

    private VideoStreamSyncPacketVO buildStreamSyncPacket(Long engineeringId, List<VideoStreamItemVO> streams) {
        VideoStreamSyncPacketVO packet = new VideoStreamSyncPacketVO();
        packet.setDataPacketID(idGenerator.nextPacketId());
        packet.setEngineeringID(engineeringId);
        packet.setPublishTime(timeFormat.format(new Date()));
        packet.setMediaServerIp(properties.getSipIp());
        packet.setStreams(streams);
        return packet;
    }

    private VideoStreamItemVO toStreamItem(VideoDeviceItemVO device, VideoChannelVO channel, VideoPlayInfoVO playInfo) {
        VideoStreamItemVO item = new VideoStreamItemVO();
        item.setDeviceId(device.getDeviceId());
        item.setDeviceName(device.getDeviceName());
        item.setChannelId(channel.getChannelId());
        item.setChannelName(channel.getChannelName());
        item.setStreamStatus(playInfo.getStreamStatus());
        item.setPlayUrl(playInfo.getPreferredPlayUrl(properties.getPreferredProtocol()));
        item.setFlvUrl(playInfo.getFlvUrl());
        item.setHlsUrl(playInfo.getHlsUrl());
        item.setRtspUrl(playInfo.getRtspUrl());
        return item;
    }

    private VideoStreamItemVO toFailedStreamItem(VideoDeviceItemVO device, VideoChannelVO channel, String message) {
        VideoStreamItemVO item = toStreamItem(device, channel, new VideoPlayInfoVO());
        item.setStreamStatus("FAILED");
        item.setPlayUrl(message);
        return item;
    }

    private static void validateIds(String deviceId, String channelId) {
        if (!StringUtils.hasText(deviceId) || !StringUtils.hasText(channelId)) {
            throw new BusinessException("deviceId 与 channelId 不能为空");
        }
    }

    private static String sessionKey(String deviceId, String channelId) {
        return deviceId + "_" + channelId;
    }

    private String resolveStreamTopic() {
        if (StringUtils.hasText(properties.getMqttStreamTopic())) {
            return properties.getMqttStreamTopic();
        }
        return MqttTopics.IotInfo.VIDEO_STREAM.getTopic();
    }
}
