using Ayay.SerilogLogs;
using OpenCvSharp;
using Serilog;
using SHH.CameraSdk.HikFeatures;
using System.Runtime.ExceptionServices;
using System.Security;

namespace SHH.CameraSdk;

/// <summary>
/// [海康驱动] 工业级视频源实现 V3.5.0 (运维增强版)
/// <para>技术支撑：基于 Hikvision CH-NetSDK V6.1.x 开发</para>
/// <para>核心职责：深度封装海康私有协议，实现从原始私有码流到 BGR 零拷贝帧池的高性能转换与分发</para>
/// 关键修复与增强记录：
/// <para>✅ 1. [Fix Bug Z] 架构修正：移除子类冗余的 Controller 定义，强制复用基类 <see cref="BaseVideoSource.Controller"/>，彻底修复多路并发下的 FPS 流控失效问题</para>
/// <para>✅ 2. [Feat A] 热更新支持：重写 <see cref="OnApplyOptions"/>，实现码流类型(Main/Sub)与渲染句柄的动态热切换，无需重启设备链路即可生效</para>
/// <para>✅ 3. [Feat B] 运维集成：全链路接入 <see cref="BaseVideoSource.AddAuditLog"/>，将登录、取流、重连及 SDK 报警实时推送至 Web 运维仪表盘</para>
/// <para>✅ 4. [Feat C] 性能优化：在解码回调中使用 <see cref="Monitor.TryEnter"/> 竞争锁，有效规避在设备断开瞬间可能产生的驱动层死锁</para>
/// </summary>
public class HikVideoSource : BaseVideoSource, 
    IHikContext, ITimeSyncFeature, IRebootFeature, IPtzFeature
{
    #region --- 1. 静态资源与全局路由 (Static Resources) ---

    /// <summary> 海康 SDK 专用日志实例 </summary>
    protected override ILogger _sdkLog => Log.ForContext("SourceContext", LogModules.HikVisionSdk);

    /// <summary> 全局句柄映射表：用于静态异常回调分发至具体实例 </summary>
    private static readonly ConcurrentDictionary<int, HikVideoSource> _instances = new();

    /// <summary> 静态异常回调委托引用 </summary>
    private static readonly HikNativeMethods.EXCEPTION_CALLBACK _globalExceptionCallback = StaticOnSdkException;
    
    /// <summary> 全局播放端口抢占锁 </summary>
    private static readonly object _globalPortLock = new();

    #endregion

    #region --- 2. 实例成员与组件引用 (Instance Members) ---

    // 协议功能组件
    private readonly HikTimeSyncProvider _timeProvider;
    private readonly HikRebootProvider _rebootProvider;
    private readonly HikPtzProvider _ptzProvider;

    // SDK 句柄与资源
    private int _userId = -1;       // SDK 登录句柄
    private int _realPlayHandle = -1; // 预览句柄
    private int _playPort = -1;       // 播放端口

    // 同步控制
    private readonly object _initLock = new();      // 登录句柄初始化锁
    private readonly object _bufferLock = new();    // 帧缓冲锁
    private volatile int _connectionEpoch = 0;      // 连接轮询版本号

    // 回调委托（强引用防止GC回收）
    private readonly HikNativeMethods.REALDATACALLBACK _realDataCallBack;
    private readonly HikPlayMethods.DECCBFUN _decCallBack;

    // 图像处理资源, 内存复用对象
    private Mat? _sharedYuvMat;
    private Mat? _sharedBgrMat;
    private FramePool? _framePool;
    private bool _isPoolReady = false;

    #endregion

    #region --- 3. 构造函数 (Constructor) ---

    /// <summary>
    /// 海康视频源实现
    /// </summary>
    /// <param name="config"></param>
    public HikVideoSource(VideoSourceConfig config) : base(config)
    {
        // 初始化组件，将 "this" 作为上下文传进去
        _timeProvider = new HikTimeSyncProvider(this);
        _rebootProvider = new HikRebootProvider(this);
        _ptzProvider = new HikPtzProvider(this);

        // Modified: [Fix GC Crash] 移除此处的 new REALDATACALLBACK
        // 直接使用构造函数初始化的 _realDataCallBack，保证委托地址在整个对象生命周期内不变
        _realDataCallBack = new HikNativeMethods.REALDATACALLBACK(SafeOnRealDataReceived);
        _decCallBack = new HikPlayMethods.DECCBFUN(SafeOnDecodingCallBack);
    }

    #endregion

    #region --- 4. 接口实现：IHikContext & Features (Interface Impls) ---

    /// <summary>
    /// 获取登录句柄
    /// </summary>
    /// <returns></returns>
    public int GetUserId() => _userId; // 暴露父类或私有的 _userId
    
    /// <summary>
    /// 获取设备IP
    /// </summary>
    /// <returns></returns>
    public string GetDeviceIp() => Config.IpAddress;

    /// <summary>
    /// 核心逻辑：全部委托给 _timeProvider 处理，自己不写一行逻辑
    /// </summary>
    /// <returns></returns>
    public Task<DateTime> GetTimeAsync() => _timeProvider.GetTimeAsync();

    /// <summary>
    /// 设置设备时间
    /// </summary>
    /// <param name="time"></param>
    /// <returns></returns>
    public Task SetTimeAsync(DateTime time) => _timeProvider.SetTimeAsync(time);

    /// <summary>
    /// 重启设备
    /// </summary>
    /// <returns></returns>
    public Task RebootAsync() => _rebootProvider.RebootAsync();

    /// <summary>
    /// PTZ 控制
    /// </summary>
    /// <param name="action"></param>
    /// <param name="stop"></param>
    /// <param name="speed"></param>
    /// <returns></returns>
    public Task PtzControlAsync(PtzAction action, bool stop, int speed = 4)
        => _ptzProvider.PtzControlAsync(action, stop, speed);

    /// <summary>
    /// PTZ 步长
    /// </summary>
    /// <param name="action"></param>
    /// <param name="durationMs"></param>
    /// <param name="speed"></param>
    /// <returns></returns>
    public Task PtzStepAsync(PtzAction action, int durationMs, int speed = 4)
        => _ptzProvider.PtzStepAsync(action, durationMs, speed);

    #endregion

    #region --- 5. 生命周期重写 (Lifecycle Overrides) ---

    /// <summary>
    /// 启动逻辑
    /// </summary>
    /// <param name="token"></param>
    /// <returns></returns>
    /// <exception cref="CameraException"></exception>
    /// <exception cref="OperationCanceledException"></exception>
    protected override async Task OnStartAsync(CancellationToken token)
    {
        int currentEpoch = Interlocked.Increment(ref _connectionEpoch);

        await Task.Run(() =>
        {
            if (currentEpoch != _connectionEpoch) return;

            if (!HikSdkManager.Initialize())
            {
                _sdkLog.Error("[SDK] HikVision Sdk 初始化失败.");
                AddAuditLog($"[SDK] HikVision Sdk 初始化失败.");

                throw new CameraException(CameraErrorCode.SdkNotInitialized, "HikVision Sdk 初始化失败.", DeviceBrand.HikVision);
            }

            try
            {
                HikNativeMethods.NET_DVR_SetExceptionCallBack_V30(0, IntPtr.Zero, _globalExceptionCallback, IntPtr.Zero);

                // [审计] 记录登录动作
                _sdkLog.Information($"[SDK] Hik 正在执行登录 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}");
                AddAuditLog($"[SDK] Hik 正在执行登录 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}");

                var devInfo = new HikNativeMethods.NET_DEVICEINFO_V30();
                int newUserId = HikNativeMethods.NET_DVR_Login_V30(
                    _config.IpAddress, _config.Port, _config.Username, _config.Password, ref devInfo);

                if (currentEpoch != _connectionEpoch)
                {
                    if (newUserId >= 0) HikNativeMethods.NET_DVR_Logout(newUserId);
                    _sdkLog.Information($"[SDK] Hik 启动任务已过期 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}");
                    AddAuditLog($"[SDK] Hik 启动任务已过期 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}");
                    throw new OperationCanceledException("启动任务已过期");
                }

                if (newUserId < 0)
                {
                    uint err = HikNativeMethods.NET_DVR_GetLastError();

                    _sdkLog.Warning($"[SDK] Hik 登录失败 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}");
                    AddAuditLog($"[SDK] Hik 登录失败 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}");
                    throw new CameraException(HikErrorMapper.Map(err), $"登录失败: {err}", DeviceBrand.HikVision, (int)err);
                }

                _userId = newUserId;

                _instances.TryAdd(_userId, this);

                _sdkLog.Information($"[SDK] Hik 登录成功 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}");
                AddAuditLog($"[SDK] Hik 登录成功 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}");

                // 开启取流
                if (!StartRealPlay())
                {
                    uint err = HikNativeMethods.NET_DVR_GetLastError();
                    throw new CameraException(HikErrorMapper.Map(err), $"预览失败: {err}", DeviceBrand.HikVision, (int)err);
                }

                _sdkLog.Information($"[SDK] Hik 网络取流成功 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}");
                AddAuditLog($"[SDK] Hik 网络取流成功 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}");
            }
            catch (Exception ex)
            {
                _sdkLog.Error($"[SDK] Hik 启动异常. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}, " + "Exception:{Exp}", ex);
                AddAuditLog($"[SDK] Hik 启动异常. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}, Exception:{ex.Message}");
                CleanupSync();
                throw;
            }
        }, token);
    }

    /// <summary>
    /// 停止逻辑
    /// </summary>
    /// <returns></returns>
    protected override async Task OnStopAsync()
    {
        Interlocked.Increment(ref _connectionEpoch);

        _sdkLog.Debug($"[SDK] Hik 正在执行停止流程. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}");
        AddAuditLog($"[SDK] Hik 正在执行停止流程. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}");

        await Task.Run(() => CleanupSync());

        _sdkLog.Information($"[SDK] Hik 设备已停止. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}");
        AddAuditLog($"[SDK] Hik 设备已停止. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}");
    }

    /// <summary>
    /// 同步清理所有 SDK 资源
    /// </summary>
    private void CleanupSync()
    {
        lock (_initLock)
        {
            // 1. 停止预览
            try
            {
                if (_realPlayHandle >= 0)
                {
                    HikNativeMethods.NET_DVR_StopRealPlay(_realPlayHandle);
                    _realPlayHandle = -1;
                }
            }
            catch(Exception ex)
            {
                _sdkLog.Debug($"[SDK] Hik 停止预览失败. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}" + "Exception: {Exp}", ex);
                AddAuditLog($"[SDK] Hik 停止预览失败. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId} Exception: {ex.Message}");
            }

            // 2. 停止解码
            if (_playPort >= 0)
            {
                lock(_globalPortLock)
                {
                    try
                    {
                        HikPlayMethods.PlayM4_Stop(_playPort);
                        HikPlayMethods.PlayM4_CloseStream(_playPort);
                    }
                    catch (Exception ex)
                    {
                        _sdkLog.Debug($"[SDK] Hik 停止解码失败. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}" + "Exception: {Exp}", ex);
                        AddAuditLog($"[SDK] Hik 停止解码失败. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId} Exception: {ex.Message}");
                    }
                    finally
                    {
                        try
                        {
                            HikPlayMethods.PlayM4_FreePort(_playPort);
                        }
                        catch (Exception ex)
                        {
                            _sdkLog.Warning($"[SDK] Hik 端口资源释放失败. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}" + "Exception: {Exp}", ex);
                            AddAuditLog($"[SDK] Hik 端口资源释放失败. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId} Exception: {ex.Message}");
                        }
                    }

                    _playPort = -1;
                }
            }

            lock (_bufferLock)
            {
                _sharedYuvMat?.Dispose(); _sharedYuvMat = null;
                _sharedBgrMat?.Dispose(); _sharedBgrMat = null;
            }

            // 3. 注销登录
            try
            {
                if (_userId >= 0)
                {
                    _instances.TryRemove(_userId, out _);
                    HikNativeMethods.NET_DVR_Logout(_userId);
                    _userId = -1;
                }
            }
            catch (Exception ex)
            {
                _sdkLog.Warning($"[SDK] Hik 注销登录失败. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}" + "Exception: {Exp}", ex);
                AddAuditLog($"[SDK] Hik 注销登录失败. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId} Exception: {ex.Message}");
            }

            _framePool?.Dispose();
            _framePool = null;
            _isPoolReady = false;
        }

        try
        {
            HikSdkManager.Uninitialize();
        }
        catch
        {
        }
    }

    /// <summary>
    /// 获取设备元数据
    /// </summary>
    /// <returns></returns>
    protected override Task<DeviceMetadata> OnFetchMetadataAsync() => Task.FromResult(new DeviceMetadata());
    
    #endregion

    #region --- 6. 驱动逻辑：热切换重写 (OnApplyOptions) ---

    /// <summary>
    /// 配置更新
    /// </summary>
    /// <param name="options"></param>
    protected override void OnApplyOptions(DynamicStreamOptions options)
    {
        // 1. 码流热切换逻辑
        if (options.StreamType.HasValue)
        {
            int targetStream = options.StreamType.Value;

            _sdkLog.Debug($"[SDK] Hik 收到码流切换请求. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}, 播放句柄:{_realPlayHandle}");
            AddAuditLog($"收到码流切换请求: {targetStream}，开始执行热切换...");

            lock (_initLock)
            {
                // 【修复点】双重检查在线状态
                // 如果在拿锁的过程中，外部已经调用了 StopAsync，这里必须停止，否则会创建"僵尸句柄"
                if (!IsActived || !IsPhysicalOnline || _userId < 0)
                {
                    _sdkLog.Warning($"[SDK] 码流切换被取消，设备已离线.");
                    AddAuditLog($"[SDK] 码流切换被取消，设备已离线.");
                    return;
                }

                // A. 停止预览 (Keep Login)
                if (_realPlayHandle >= 0)
                {
                    HikNativeMethods.NET_DVR_StopRealPlay(_realPlayHandle);
                    _realPlayHandle = -1;
                }

                // B. 清理播放库 (防止旧流数据残留)
                if (_playPort >= 0)
                {
                    HikPlayMethods.PlayM4_Stop(_playPort);
                    HikPlayMethods.PlayM4_CloseStream(_playPort);
                    HikPlayMethods.PlayM4_FreePort(_playPort);
                    _playPort = -1;
                }

                // C. 更新内部配置状态
                _config.StreamType = targetStream;

                // D. 重新开启预览
                if (StartRealPlay())
                {
                    _sdkLog.Information($"[SDK] Hik 码流热切换成功. => 当前: {(_config.StreamType == 0 ? "主" : "子")}码流), ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}, 播放句柄:{_realPlayHandle}");
                    AddAuditLog($"[SDK] Hik 码流热切换成功. => 当前: {(_config.StreamType == 0 ? "主" : "子")}码流), ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}, 播放句柄:{_realPlayHandle}");
                }
                else
                {
                    uint err = HikNativeMethods.NET_DVR_GetLastError();
                    _sdkLog.Information($"[SDK] Hik 码流切换失败. => Err:{err}, ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}, 播放句柄:{_realPlayHandle}");
                    AddAuditLog($"[SDK] Hik 码流切换失败. => Err:{err}, ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}, 播放句柄:{_realPlayHandle}");

                    // 【修复点】主动上报错误，让基类感知到当前已经断流了
                    // 这会将状态置为 Reconnecting，并可能触发自动重连
                    ReportError(new CameraException(HikErrorMapper.Map(err), "Hik 码流切换失败.", DeviceBrand.HikVision));
                }
            }
        }

        // 2. 句柄动态更新逻辑 (如有需要)
        if (options.RenderHandle.HasValue)
        {
            // 如果是硬解码模式，可以在这里调用 PlayM4_Play(port, newHandle)
            _sdkLog.Information($"[SDK] Hik 收到新句柄绑定请求, 新句柄:{options.RenderHandle}. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}, 播放句柄:{_realPlayHandle}");
            AddAuditLog($"[SDK] Hik 收到新句柄绑定请求, 新句柄:{options.RenderHandle}. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}, 播放句柄:{_realPlayHandle}");
        }
    }

    #endregion

    #region --- 7. 驱动逻辑：取流与解码 (Streaming & Decoding) ---
    
    /// <summary>
    /// 开始预览
    /// </summary>
    /// <returns></returns>
    private bool StartRealPlay()
    {
        var previewInfo = new HikNativeMethods.NET_DVR_PREVIEWINFO
        {
            hPlayWnd = (IntPtr)_config.RenderHandle,
            lChannel = _config.ChannelIndex,
            dwStreamType = (uint)_config.StreamType,
            bBlocked = false
        };

        _realPlayHandle = HikNativeMethods.NET_DVR_RealPlay_V40(_userId, ref previewInfo, _realDataCallBack, IntPtr.Zero);
        
        // Optimized: [Fix GC Crash] 显式保活，防止 JIT 在 P/Invoke 过程中激进回收（双重保险）
        GC.KeepAlive(_realDataCallBack);

        return _realPlayHandle >= 0;
    }

    /// <summary>
    /// 预览数据回调
    /// </summary>
    /// <param name="lRealHandle"></param>
    /// <param name="dwDataType"></param>
    /// <param name="pBuffer"></param>
    /// <param name="dwBufSize"></param>
    /// <param name="pUser"></param>
    private void SafeOnRealDataReceived(int lRealHandle, uint dwDataType, IntPtr pBuffer, uint dwBufSize, IntPtr pUser)
    {
        try
        {
            // 【关键位置】：在此处调用，统计网络层收到的每一字节数据
            // 因为 dwBufSize > 0，MarkFrameReceived 内部只会累加码流，不会增加 FPS 计数
            MarkFrameReceived(dwBufSize);

            // Optimized: [原因] 增加前置失效判定，若当前句柄已释放则不再处理后续流数据
            if (_realPlayHandle == -1) return;

            // 处理系统头
            if (dwDataType == HikNativeMethods.NET_DVR_SYSHEAD)
            {
                lock (_initLock)
                {
                    // 原子检查：若已存在端口、预览句柄已失效或对象已销毁，则立即拦截
                    if (_realPlayHandle == -1 || _playPort != -1) return;

                    bool getPortSuccess;
                    lock (_globalPortLock)
                    {
                        getPortSuccess = HikPlayMethods.PlayM4_GetPort(ref _playPort);
                    }

                    if (!getPortSuccess) return;

                    // 配置播放库参数
                    HikPlayMethods.PlayM4_SetDisplayBuf(_playPort, 1); // 极速模式
                    HikPlayMethods.PlayM4_SetStreamOpenMode(_playPort, 0);

                    if (!HikPlayMethods.PlayM4_OpenStream(_playPort, pBuffer, dwBufSize, 2 * 1024 * 1024))
                    {
                        // 开启失败需在锁内立即释放端口，防止句柄残留
                        lock (_globalPortLock)
                        {
                            HikPlayMethods.PlayM4_FreePort(_playPort);
                            _playPort = -1;
                        }
                        return;
                    }

                    // =================================================================================
                    // 🚀 [新增代码] 尝试开启 GPU 硬解码 (1=开启, 0=关闭)
                    // 位置：必须在 OpenStream 成功之后，SetDecCallBack 之前
                    // =================================================================================
                    try
                    {
                        HikPlayMethods.PlayM4_SetHardWareDecode(_playPort, 1);
                        _sdkLog.Information($"[Perf] Hik 尝试开启硬解码. ID:{_config.Id} Port:{_playPort}");
                    }
                    catch (Exception ex)
                    {
                        // 即使失败也不影响流程，仅记录警告
                        _sdkLog.Warning($"[Perf] Hik 开启硬解码失败: {ex.Message}");
                    }

                    HikPlayMethods.PlayM4_SetDecCallBackEx(_playPort, _decCallBack, IntPtr.Zero, 0);

                    // Optimized: [Fix GC Crash] 显式保活
                    GC.KeepAlive(_decCallBack);

                    HikPlayMethods.PlayM4_Play(_playPort, IntPtr.Zero);

                    _sdkLog.Debug($"[SDK] Hik 播放端口初始化成功, ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}, 播放端口:{_playPort}");
                }
            }
            // 处理流数据
            else if (dwDataType == HikNativeMethods.NET_DVR_STREAMDATA && _playPort != -1)
            {
                HikPlayMethods.PlayM4_InputData(_playPort, pBuffer, dwBufSize);
            }
        }
        catch(Exception ex)
        {
            _sdkLog.Debug($"[SDK] Hik SafeOnRealDataReceived 异常. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}" + "Exception: {Exp}", ex);
            AddAuditLog($"[SDK] Hik SafeOnRealDataReceived 异常. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId} Exception: {ex.Message}");
        }
    }

    /// <summary>
    /// 必须同时加上 SecurityCritical
    /// </summary>
    /// <param name="nPort"></param>
    /// <param name="pBuf"></param>
    /// <param name="nSize"></param>
    /// <param name="pFrameInfo"></param>
    /// <param name="nReserved1"></param>
    /// <param name="nReserved2"></param>
    [HandleProcessCorruptedStateExceptions]
    [SecurityCritical]
    private void SafeOnDecodingCallBack(int nPort, IntPtr pBuf, int nSize, ref HikPlayMethods.FRAME_INFO pFrameInfo, int nReserved1, int nReserved2)
    {
        //Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")} 帧抵达.");

        // 1. 基础指针检查
        if (pBuf == IntPtr.Zero || nSize <= 0) return;

        // 2. 视频有效性检查
        // 如果不是音频，但宽高依然为 0，说明是异常数据或非图像私有头
        if (pFrameInfo.nWidth <= 0 || pFrameInfo.nHeight <= 0)
        {
            // 如果你想调试音频，可以在这里处理 pBuf
            // 但对于视频分析业务，这里直接 return，不要写 Log，否则磁盘会爆
            return;
        }


        // [优化] 维持心跳，防止被哨兵误杀
        MarkFrameReceived(0);

        int currentWidth = pFrameInfo.nWidth;
        int currentHeight = pFrameInfo.nHeight;

        // 3. [核心修复点] 分辨率动态监测与帧池热重建
        // Modified: [原因] 修复 Bug E：当 SDK 输出的分辨率与当前帧池尺寸不符时，必须阻塞并重建资源。
        // 严禁在分辨率不匹配的情况下调用 OpenCV 转换函数，防止非托管内存越界写入。
        if (!_isPoolReady || Width != currentWidth || Height != currentHeight)
        {
            bool lockTaken = false;
            try
            {
                // 尝试获取初始化锁，超时 50ms（分辨率变更属于低频关键动作，允许稍长等待）
                Monitor.TryEnter(_initLock, 50, ref lockTaken);

                if (lockTaken)
                {
                    // Double Check：防止多个解码回调并发重建
                    if (Width != currentWidth || Height != currentHeight || !_isPoolReady)
                    {
                        _sdkLog.Warning($"[SDK] 监测到分辨率变更: {Width}x{Height} -> {currentWidth}x{currentHeight}，正在重建帧池...");

                        // 销毁旧池（内部会释放所有 Mat 资源）
                        _framePool?.Dispose();

                        // 更新基类维护的分辨率属性
                        Width = currentWidth;
                        Height = currentHeight;

                        // 重建帧池：initialSize 设为 3 保证高并发缓冲，maxSize 设为 5 严格控制内存总额
                        _framePool = new FramePool(Width, Height, MatType.CV_8UC3, initialSize: 3, maxSize: 5);
                        _isPoolReady = true;

                        AddAuditLog($"分辨率热重载完成: {Width}x{Height}");
                    }
                }
                else
                {
                    // 拿不到锁说明主线程正在 Stop 或切换配置，直接丢弃该帧防止死锁
                    return;
                }
            }
            catch (Exception ex)
            {
                _sdkLog.Error(ex, "帧池重建失败");
                return;
            }
            finally
            {
                if (lockTaken) Monitor.Exit(_initLock);
            }
        }

        // 1. [核心流控] 询问基类控制器：这帧要不要？
        // 之前失效是因为操作的是子类被遮蔽的 Controller，现在复用基类 Controller，逻辑就通了。
        // 传入真实的输入帧率作为参考基准
        var decision = Controller.MakeDecision(Environment.TickCount64, (int)RealFps);

        // 如果没人要，直接丢弃，不进行 Mat 转换，节省 CPU
        if (!decision.IsCaptured) return;

        //Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")} 帧抵处理.");

        // Optimized: [原因] 将 smartFrame 定义在 try 外部，确保 finally 块能够可靠执行 Dispose 归还逻辑
        SmartFrame? smartFrame = null;

        try
        {
            if (_framePool == null)
            {
                _sdkLog.Warning($"[SDK] Hik framePool 为空, 丢弃. 设备ID: {Config.Id} IP:{Config.IpAddress} Name:{Config.Name}");
                return;
            }

            // 3. 转换与分发
            smartFrame = _framePool.Get();
            if (smartFrame == null) return; // 池满丢帧

            // Optimized: [原因] 使用局部作用域封装 YUV 转换，确保原生指针尽快脱离
            using (var rawYuvWrapper = Mat.FromPixelData(currentHeight + currentHeight / 2, currentWidth, MatType.CV_8UC1, pBuf))
            {
                Cv2.CvtColor(rawYuvWrapper, smartFrame.InternalMat, ColorConversionCodes.YUV2BGR_YV12);
            }

            // =========================================================
            // 【新增防御】: 检查转换结果是否有效
            // 如果转换失败，或者 Mat 为空，绝对不能传给 Router
            // =========================================================
            if (smartFrame.InternalMat.Empty())
            {
                _sdkLog.Warning($"[SDK] Hik 解码帧无效 (Empty Mat), 丢弃. 设备ID: {Config.Id} IP:{Config.IpAddress} Name:{Config.Name}");
                // finally 会负责 Dispose，这里直接返回
                return;
            }

            foreach (var targetAppId in decision.TargetAppIds)
                smartFrame.SubscriberIds.Enqueue(targetAppId);

            // =========================================================================
            // 【修正】删除这里的 GlobalStreamDispatcher.Dispatch！
            // 严禁在这里分发，因为这时的图是“生的”，还没经过 Pipeline 处理。
            // =========================================================================
            //GlobalStreamDispatcher.Dispatch(Id, smartFrame);

            // 4. [分发] 将决策结果传递给处理中心
            // decision.TargetAppIds 包含了 "谁需要这一帧" 的信息
            //GlobalProcessingCenter.Submit(this.Id, smartFrame, decision);
            GlobalPipelineRouter.Enqueue(Id, smartFrame, decision);
        }
        catch (Exception ex)
        {
            // 这里为了性能不频繁写日志，仅在调试时开启
            _sdkLog.Warning($"[SDK] Hik SafeOnDecodingCallBack 异常. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}" + "Exception: {Exp}", ex);
            AddAuditLog($"[SDK] Hik SafeOnDecodingCallBack 异常. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId} Exception: {ex.Message}");
        }
        finally
        {
            // Optimized: [原因] 驱动层必须释放它持有的初始引用。
            // 如果 Dispatch 内部已经 AddRef，此处 Dispose 只会让计数从 2 降到 1，帧不会回池。
            // 如果没有其他人持有，此处 Dispose 会让计数从 1 降到 0，帧安全回池。
            smartFrame?.Dispose();
        }
    }

    #endregion

    #region --- 8. 全局异常静态回调 ---

    /// <summary>
    /// SDK 报警回调
    /// </summary>
    /// <param name="dwType"></param>
    /// <param name="lUserID"></param>
    /// <param name="lHandle"></param>
    /// <param name="pUser"></param>
    private static void StaticOnSdkException(uint dwType, int lUserID, int lHandle, IntPtr pUser)
    {
        try
        {
            if (_instances.TryGetValue(lUserID, out var instance))
            {
                Log.ForContext("SourceContext", LogModules.HikVisionSdk)
                    .Error($"Hik SDK 报警异常: 0x{dwType:X}, UserId: {lUserID} ");

                instance.AddAuditLog($"SDK报警 User:{pUser} 异常: 0x{dwType:X}, UserId: {lUserID}"); // 写入审计
                instance.ReportError(new CameraException(
                    CameraErrorCode.NetworkUnreachable,
                    $"SDK全局异常: 0x{dwType:X}",
                    DeviceBrand.HikVision));
            }
        }
        catch { }
    }

    #endregion
}