using Grpc.Core;
using SHH.Contracts.Grpc;
using System.Collections.Concurrent;

namespace SHH.MjpegPlayer
{
    /// <summary>
    /// gRpc 会话管理器
    /// 职责：专门负责维护、检索和清理所有远程客户端（分析节点）的 gRpc 指令下发物理通道 (Stream)。
    /// 它是连接“业务逻辑”与“物理传输”的桥梁，确保指令能准确投递到对应的连接流中。
    /// </summary>
    public class GrpcSessionManager
    {
        #region 单例模式

        /// <summary>
        /// 获取会话管理器的全局单例实例。
        /// </summary>
        public static GrpcSessionManager Instance { get; } = new GrpcSessionManager();

        /// <summary>
        /// 私有构造函数，防止外部实例化。
        /// </summary>
        private GrpcSessionManager() { }

        #endregion

        #region 内部存储

        /// <summary>
        /// 物理流存储字典
        /// Key: 远程服务实例唯一 ID (InstanceId)
        /// Value: gRpc 双向流或服务端推送流的写入器句柄 (IServerStreamWriter)
        /// 使用 ConcurrentDictionary 确保在多客户端并发连接/断开时的线程安全性。
        /// </summary>
        private readonly ConcurrentDictionary<string, IServerStreamWriter<CommandPayloadProto>> _sessionStreams
            = new ConcurrentDictionary<string, IServerStreamWriter<CommandPayloadProto>>();

        #endregion

        #region 公共管理接口

        /// <summary>
        /// 注册/更新物理物理通道。
        /// 当客户端调用 OpenCommandChannel 并成功建立 Server Streaming 连接时，由 GatewayService 调用此方法。
        /// </summary>
        /// <param name="instanceId">客户端实例唯一标识</param>
        /// <param name="responseStream">该客户端对应的 gRpc 响应流句柄</param>
        public void RegisterSession(string instanceId, IServerStreamWriter<CommandPayloadProto> responseStream)
        {
            // 1. 参数校验：无效 ID 不予处理
            if (string.IsNullOrEmpty(instanceId)) return;

            // 2. 登记或覆盖物理流：
            // 如果客户端异常断开后迅速重连，此处会覆盖旧的流句柄，确保指令始终通过最新的管道下发。
            _sessionStreams[instanceId] = responseStream;

            // 3. 记录日志：便于运维监控连接状态
            Console.WriteLine($"[Session] 物理通道就绪通知 -> 节点 ID: {instanceId}, 当前在线总数: {_sessionStreams.Count}");
        }

        /// <summary>
        /// 移除物理通道。
        /// 当 gRpc 连接由于网络波动、客户端崩溃或主动关闭而断开时，由 GatewayService 的 finally 块调用。
        /// </summary>
        /// <param name="instanceId">要注销的客户端实例 ID</param>
        public void RemoveSession(string instanceId)
        {
            // 1. 参数校验
            if (string.IsNullOrEmpty(instanceId)) return;

            // 2. 安全移除：若 ID 存在则移除并释放相关内部引用
            if (_sessionStreams.TryRemove(instanceId, out _))
            {
                Console.WriteLine($"[Session] 物理通道移除通知 -> 节点 ID: {instanceId}, 剩余在线总数: {_sessionStreams.Count}");
            }
        }

        /// <summary>
        /// 检索目标节点的物理流句柄。
        /// 供 MessageBus 使用，它是指令下发前定位物理路径的关键步骤。
        /// </summary>
        /// <param name="instanceId">目标节点的唯一 ID</param>
        /// <returns>返回对应的 IServerStreamWriter 实例；若节点不在线则返回 null</returns>
        public IServerStreamWriter<CommandPayloadProto> GetSession(string instanceId)
        {
            // 1. 参数校验
            if (string.IsNullOrEmpty(instanceId)) return null;

            // 2. 尝试从缓存字典中获取流句柄
            _sessionStreams.TryGetValue(instanceId, out var stream);

            return stream;
        }

        /// <summary>
        /// 检查指定节点是否处于物理连接状态。
        /// </summary>
        /// <param name="instanceId">实例 ID</param>
        /// <returns>True 表示物理通道已建立</returns>
        public bool IsSessionActive(string instanceId)
        {
            return !string.IsNullOrEmpty(instanceId) && _sessionStreams.ContainsKey(instanceId);
        }

        #endregion
    }
}