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

namespace SHH.CameraSdk;

/// <summary>
/// [大华驱动] 工业级视频源实现 (依照官方 Demo 逻辑重构版)
/// <para>当前模块: AiVideo | 核心原则: 低耦合、高并发、零拷贝</para>
/// </summary>
public class DahuaVideoSource : BaseVideoSource
{
    protected override ILogger _sdkLog => Log.ForContext("SourceContext", LogModules.DaHuaSdk);

    #region --- 1. 静态资源与回调持有 (Static Resources) ---

    private static readonly ConcurrentDictionary<IntPtr, DahuaVideoSource> _instances = new();

    // 必须保持静态引用，防止被 GC 回收导致回调崩溃
    private static fDisConnectCallBack m_DisConnectCallBack;
    private static fHaveReConnectCallBack m_ReConnectCallBack;
    private static fRealDataCallBackEx2 m_RealDataCallBackEx2;

    #endregion

    #region --- 2. 实例成员 (Instance Members) ---

    private IntPtr _loginId = IntPtr.Zero;
    private IntPtr _realPlayId = IntPtr.Zero;
    private int _playPort = -1;

    private FramePool? _framePool;
    private volatile bool _isPoolReady = false;
    private readonly object _initLock = new();

    // 强引用实例的回调委托
    private DahuaPlaySDK.DECCBFUN? _decCallBack;

    #endregion

    public DahuaVideoSource(VideoSourceConfig config) : base(config) { }

    #region --- 3. 生命周期实现 (Lifecycle Overrides) ---

    protected override async Task OnStartAsync(CancellationToken token)
    {
        await Task.Run(() =>
        {
            // 1. 初始化 SDK (只需一次)
            InitSdkGlobal();

            _sdkLog.Information($"[SDK] Dahua 正在执行登录 => ID:{_config.Id} IP:{_config.IpAddress}");
            AddAuditLog($"[SDK] Dahua 正在执行登录 => IP:{_config.IpAddress}");

            // 2. 登录设备
            NET_DEVICEINFO_Ex deviceInfo = new NET_DEVICEINFO_Ex();
            _loginId = NETClient.LoginWithHighLevelSecurity(_config.IpAddress, (ushort)_config.Port, _config.Username, _config.Password, EM_LOGIN_SPAC_CAP_TYPE.TCP, IntPtr.Zero, ref deviceInfo);

            if (_loginId == IntPtr.Zero)
            {
                string err = NETClient.GetLastError();
                throw new Exception($"大华登录失败: {err}");
            }

            _instances.TryAdd(_loginId, this);
            _sdkLog.Information($"[SDK] Dahua 登录成功 => LoginID:{_loginId}, 通道数:{deviceInfo.nChanNum}");

            // 3. 开启实时预览 (参考 Demo Button1_Click 逻辑)
            // Modified: [原因] 使用 RealPlayByDataType 以支持获取原始码流并指定回调
            NET_IN_REALPLAY_BY_DATA_TYPE stuIn = new NET_IN_REALPLAY_BY_DATA_TYPE()
            {
                dwSize = (uint)Marshal.SizeOf(typeof(NET_IN_REALPLAY_BY_DATA_TYPE)),
                nChannelID = _config.ChannelIndex,
                hWnd = IntPtr.Zero, // 不直接渲染到窗口，我们拿原始数据
                // EM_A_RType_Realplay = 0 (主码流), EM_A_RType_Realplay_1 = 1 (辅码流)
                rType = _config.StreamType == 0 ? EM_RealPlayType.EM_A_RType_Realplay : EM_RealPlayType.EM_A_RType_Realplay_1,
                emDataType = EM_REAL_DATA_TYPE.PRIVATE, // 私有码流
                cbRealDataEx = m_RealDataCallBackEx2 // 挂载静态回调
            };

            NET_OUT_REALPLAY_BY_DATA_TYPE stuOut = new NET_OUT_REALPLAY_BY_DATA_TYPE()
            {
                dwSize = (uint)Marshal.SizeOf(typeof(NET_OUT_REALPLAY_BY_DATA_TYPE))
            };

            _realPlayId = NETClient.RealPlayByDataType(_loginId, stuIn, ref stuOut, 5000);

            if (_realPlayId == IntPtr.Zero)
            {
                string err = NETClient.GetLastError();
                NETClient.Logout(_loginId);
                throw new Exception($"大华预览失败: {err}");
            }

            _sdkLog.Information($"[SDK] Dahua 取流成功 => RealPlayID:{_realPlayId}");
            AddAuditLog($"[SDK] Dahua 取流成功");

        }, token);
    }

    protected override async Task OnStopAsync()
    {
        await Task.Run(() =>
        {
            lock (_initLock)
            {
                if (_realPlayId != IntPtr.Zero)
                {
                    NETClient.StopRealPlay(_realPlayId);
                    _realPlayId = IntPtr.Zero;
                }

                if (_playPort != -1)
                {
                    DahuaPlaySDK.PLAY_Stop(_playPort);
                    DahuaPlaySDK.PLAY_CloseStream(_playPort);
                    DahuaPlaySDK.PLAY_ReleasePort(_playPort);
                    _playPort = -1;
                }

                if (_loginId != IntPtr.Zero)
                {
                    _instances.TryRemove(_loginId, out _);
                    NETClient.Logout(_loginId);
                    _loginId = IntPtr.Zero;
                }

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

    #endregion

    #region --- 4. 核心逻辑：解码与分发 (Core Logic) ---

    /// <summary>
    /// 静态回调：分发数据至具体实例
    /// </summary>
    private static void OnRealDataReceived(IntPtr lRealHandle, uint dwDataType, IntPtr pBuffer, uint dwBufSize, IntPtr param, IntPtr dwUser)
    {
        // 大华 SDK 无法直接在回调中给实例，需要通过句柄查找或全局状态
        // 简化处理：由于 Ayay 目前追求单机性能，这里假设通过全局查找或在 RealPlayByDataType 时传入的 dwUser 识别
        // 这里基于实例管理的简单逻辑：
        foreach (var instance in _instances.Values)
        {
            if (instance._realPlayId == lRealHandle)
            {
                instance.ProcessRealData(dwDataType, pBuffer, dwBufSize);
                break;
            }
        }
    }

    private void ProcessRealData(uint dwDataType, IntPtr pBuffer, uint dwBufSize)
    {
        MarkFrameReceived(dwBufSize); // 统计网络带宽

        // dwDataType: 0 = 私有码流
        if (dwDataType == 0 && dwBufSize > 0)
        {
            lock (_initLock)
            {
                if (_realPlayId == IntPtr.Zero) return;

                if (_playPort == -1)
                {
                    int port = 0;
                    if (DahuaPlaySDK.PLAY_GetFreePort(ref port))
                    {
                        _playPort = port;
                        DahuaPlaySDK.PLAY_SetStreamOpenMode(_playPort, 0);

                        // 打开流
                        DahuaPlaySDK.PLAY_OpenStream(_playPort, IntPtr.Zero, 0, 1024 * 1024 * 2);

                        // =================================================================================
                        // 🚀 [新增代码] 性能优化：尝试开启大华 GPU 硬解码
                        // 位置：必须在 PLAY_OpenStream 之后，PLAY_Play 之前
                        // =================================================================================
                        try
                        {
                            // nDecodeEngine: 1 = 开启硬解码 (Nvidia/Intel)
                            // 注意：大华 SDK 若不支持会自动降级，try-catch 仅为了防止 P/Invoke 签名缺失崩溃
                            // Optimized: 使用新版接口开启硬件解码，优先尝试 CUDA 以保证 Ayay 的多路并发性能
                            // nPort 是通过 PLAY_GetFreePort 获取的播放通道号
                            bool success = PLAY_SetEngine(_playPort, DecodeType.DECODE_HW_NV_CUDA, RenderType.RENDER_D3D11);

                            if (!success)
                            {
                                // 如果显卡不支持 CUDA，降级为普通硬解或软解
                                PLAY_SetEngine(_playPort, DecodeType.DECODE_HW, RenderType.RENDER_D3D9);
                            }
                            _sdkLog.Information($"[Perf] Dahua 尝试开启硬解码. ID:{_config.Id} Port:{_playPort}");
                        }
                        catch (Exception ex)
                        {
                            _sdkLog.Warning($"[Perf] Dahua 开启硬解码失败: {ex.Message}");
                        }

                        // 设置回调与播放
                        _decCallBack = new DahuaPlaySDK.DECCBFUN(SafeOnDecodingCallBack);
                        DahuaPlaySDK.PLAY_SetDecCallBack(_playPort, _decCallBack);
                        DahuaPlaySDK.PLAY_Play(_playPort, IntPtr.Zero);
                    }
                }

                if (_playPort != -1)
                {
                    DahuaPlaySDK.PLAY_InputData(_playPort, pBuffer, dwBufSize);
                }
            }
        }
    }

    /// <summary>
    /// 解码回调：YUV -> SmartFrame -> Pipeline
    /// <para>已集成：平滑重建、引用交换、Finally释放、[诊断陷阱]、[空帧防御]</para>
    /// </summary>
    [HandleProcessCorruptedStateExceptions] // 捕获非托管状态损坏异常 (AccessViolation)
    [SecurityCritical]
    private void SafeOnDecodingCallBack(int nPort, IntPtr pBuf, int nSize, ref DahuaPlaySDK.FRAME_INFO pFrameInfo, IntPtr nUser, int nReserved2)
    {
        // 1. 基础指针检查
        if (pBuf == IntPtr.Zero || nSize <= 0) return;

        // 2. [关键] 音频过滤：如果类型是音频，直接静默返回，防止刷屏日志
        // 大华 SDK 文档：nType >= 100 通常是音频
        if (pFrameInfo.nType >= 100)
        {
            // 这是一个音频包 (Size=2048 来源于此)，不需要记录 Warning，直接丢弃即可
            return;
        }

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

        MarkFrameReceived(0); // 维持心跳 (告诉 Watchdog 我还活着)

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

        // =========================================================================================
        // 🛡️ [第二道防线：防死锁重建] 帧池平滑重建逻辑
        // 场景：分辨率变化（主辅码流切换/ROI裁剪）。
        // 作用：使用 TryEnter 替代 lock，防止主线程 Stop 时此处死等导致死锁。
        // =========================================================================================
        if (!_isPoolReady || Width != currentWidth || Height != currentHeight)
        {
            bool lockTaken = false;
            try
            {
                // 等待 50ms，拿不到锁就放弃这一帧
                Monitor.TryEnter(_initLock, 50, ref lockTaken);

                if (lockTaken)
                {
                    // Double Check
                    if (!_isPoolReady || Width != currentWidth || Height != currentHeight)
                    {
                        _sdkLog.Information($"[Res] 大华分辨率变更: {Width}x{Height} -> {currentWidth}x{currentHeight}");

                        _framePool?.Dispose(); // 销毁旧池

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

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

                        _isPoolReady = true;
                    }
                }
                else return; // 竞争失败（可能正在停止），直接丢弃，防止卡死
            }
            catch (Exception ex)
            {
                _sdkLog.Error(ex, "[SDK] 大华帧池重建失败");
                return;
            }
            finally { if (lockTaken) Monitor.Exit(_initLock); }
        }

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

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

        SmartFrame? smartFrame = null;
        try
        {
            smartFrame = _framePool?.Get();
            if (smartFrame == null) return; // 池满丢帧

            // =========================================================================================
            // ⚡ [核心操作：零拷贝转换]
            // 大华 PlaySDK 默认输出 I420 (YUV420P)。
            // 使用 Mat.FromPixelData 封装指针，避免内存拷贝。
            // =========================================================================================
            using (var yuvMat = Mat.FromPixelData(currentHeight + currentHeight / 2, currentWidth, MatType.CV_8UC1, pBuf))
            {
                Cv2.CvtColor(yuvMat, smartFrame.InternalMat, ColorConversionCodes.YUV2BGR_I420);
            }

            // =========================================================================================
            // 🛡️ [第三道防线：空结果防御]
            // 场景：虽然入参合法，但 OpenCV 转换可能失败，或者 Mat 内部状态异常。
            // 作用：绝对禁止将 Empty Mat 传给 Worker，否则 Worker 里的 Resize 会 100% 崩溃。
            // =========================================================================================
            if (smartFrame.InternalMat.Empty())
            {
                _sdkLog.Warning($"[SDK] Dahua 解码后 Mat 为空 (转换失败), 丢弃. ID:{Config.Id}");
                return; // finally 会负责 Dispose，这里直接安全退出
            }

            // 填充订阅者
            foreach (var appId in decision.TargetAppIds)
                smartFrame.SubscriberIds.Enqueue(appId);

            // 发送到全局路由 (注意：Router 内部必须 AddRef)
            GlobalPipelineRouter.Enqueue(Id, smartFrame, decision);
        }
        catch (Exception ex)
        {
            // 捕获所有异常，包括非托管转换错误，防止回调线程崩溃带崩整个程序
            _sdkLog.Error(ex, $"[SDK] Dahua 解码/转换流程异常. ID:{Config.Id} Size:{currentWidth}x{currentHeight}");
            AddAuditLog($"[SDK] Dahua SafeOnDecodingCallBack 异常. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, Exception: {ex.Message}");
        }
        finally
        {
            // =========================================================================================
            // ♻️ [引用闭环]
            // 驱动层完成了它的任务，必须释放它持有的那次引用 (Count 1 -> 0, 或 2 -> 1)。
            // 只有这样，当 Worker 处理完 Dispose 后，帧才能真正回到池子里。
            // =========================================================================================
            smartFrame?.Dispose();
        }
    }

    #endregion

    #region --- 5. 静态初始化器 (Statics) ---

    private static void InitSdkGlobal()
    {
        if (m_RealDataCallBackEx2 != null) return;

        m_DisConnectCallBack = (lLoginID, pchDVRIP, nDVRPort, dwUser) => { };
        m_ReConnectCallBack = (lLoginID, pchDVRIP, nDVRPort, dwUser) => { };
        m_RealDataCallBackEx2 = new fRealDataCallBackEx2(OnRealDataReceived);

        NETClient.Init(m_DisConnectCallBack, IntPtr.Zero, null);
        NETClient.SetAutoReconnect(m_ReConnectCallBack, IntPtr.Zero);
    }

    protected override void OnApplyOptions(DynamicStreamOptions options) { }
    protected override Task<DeviceMetadata> OnFetchMetadataAsync() => Task.FromResult(new DeviceMetadata());

    #endregion
}