using Ayay.SerilogLogs;
using Serilog;
using System.Net.NetworkInformation;
using System.Net.Sockets;

namespace SHH.CameraSdk;

/// <summary>
/// [调度协调层] 视频自愈调度器 (V3.3.4 流量削峰版)
/// 核心职责：监控所有相机设备的运行状态，实现断线自动重连、僵死状态复位，保障视频流稳定
/// 核心修复：
/// <para>1. [Bug τ] 线程池保护：引入并发节流阀，限制同时重连/探测的任务数，防止线程池饥饿</para>
/// </summary>
public class CameraCoordinator
{
    #region --- 私有资源与配置 (Private Resources & Configurations) ---

    private ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);

    /// <summary> 已注册的相机设备集合（线程安全，支持并发添加与遍历） </summary>
    private readonly ConcurrentBag<BaseVideoSource> _cameras = new();

    /// <summary> 全局登录单行道锁 </summary>
    /// <remarks> 限制同一时刻仅允许一个相机执行登录操作，避免 SDK 登录冲突 </remarks>
    private readonly SemaphoreSlim _sdkLoginLock = new(1, 1);

    /// <summary> 并发节流阀：限制同时进行探测/重连的任务数 </summary>
    /// <remarks> 最大并发数 8，避免百路相机同时重连导致 CPU 峰值过高、UI 卡顿 </remarks>
    private readonly SemaphoreSlim _concurrencyLimiter = new(8);

    /// <summary> 相机流存活判定阈值（秒）：超过该时间无帧则判定为流中断 </summary>
    /// 海康 SDK 建立连接+首帧到达通常需 4-8 秒。阈值低了会导致刚连上就被误判为僵死而强制断开。
    private const int StreamAliveThresholdSeconds = 15;

    /// <summary> Ping 探测超时时间（毫秒） </summary>
    private const int PingTimeoutMs = 800;

    /// <summary> TCP 探测超时时间（毫秒） </summary>
    private const int TcpTimeoutMs = 1000;

    /// <summary> 调度循环间隔（毫秒）：每 5 秒执行一次全量设备状态校验 </summary>
    private const int CoordinationLoopIntervalMs = 5000;

    #endregion

    #region --- 相机注册 (Camera Registration) ---

    /// <summary>
    /// 注册相机设备到调度器
    /// 功能：将相机纳入全局状态监控与自愈管理
    /// </summary>
    /// <param name="camera">待注册的相机设备实例</param>
    public void Register(BaseVideoSource camera) => _cameras.Add(camera);

    #endregion

    #region --- 核心调度循环 (Core Coordination Loop) ---

    /// <summary>
    /// 启动调度协调循环（长期运行任务）
    /// 功能：周期性校验所有相机状态，执行自愈逻辑，支持取消
    /// </summary>
    /// <param name="token">取消令牌：用于终止调度循环</param>
    public async Task RunCoordinationLoopAsync(CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            try
            {
                // 削峰填谷式调度：通过并发节流阀控制任务并发数
                var tasks = _cameras.Select(async cam =>
                {
                    // 申请“重连/探测许可证”，无可用许可时阻塞等待
                    await _concurrencyLimiter.WaitAsync(token).ConfigureAwait(false);
                    try
                    {
                        // 安全执行状态调和（隔离单个相机的异常）
                        await SafeReconcileAsync(cam, token).ConfigureAwait(false);
                    }
                    finally
                    {
                        // 释放许可，允许其他相机执行任务
                        _concurrencyLimiter.Release();
                    }
                });

                // 等待所有相机的调和任务完成
                await Task.WhenAll(tasks).ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
                // 收到取消信号，退出循环
                break;
            }
            catch (Exception ex)
            {
                // 捕获调度层全局异常，避免循环终止
                _sysLog.Error($"[Coordinator] 调度循环异常: {ex.Message}");
            }

            try
            {
                // 等待下一个调度周期（支持响应取消）
                await Task.Delay(CoordinationLoopIntervalMs, token).ConfigureAwait(false);
            }
            catch
            {
                // 延迟过程中收到取消信号，退出循环
                break;
            }
        }
    }

    #endregion

    #region --- 安全调和包装 (Safe Reconciliation Wrapper) ---

    /// <summary>
    /// 安全执行相机状态调和
    /// 功能：隔离单个相机的异常，避免影响其他相机的调和逻辑
    /// </summary>
    /// <param name="cam">待调和的相机设备</param>
    /// <param name="token">取消令牌</param>
    private async Task SafeReconcileAsync(BaseVideoSource cam, CancellationToken token)
    {
        try
        {
            await ReconcileAsync(cam, token).ConfigureAwait(false);
        }
        catch
        {
            // 吞没单个相机的异常，确保其他相机正常调度
        }
    }

    #endregion

    #region --- 状态调和逻辑 (Reconciliation Logic) ---

    private const int NormalRetryMs = 30000;    // 普通网络故障：30秒后重试

    private const int FatalRetryMs = 900000;    // 认证类致命故障：15分钟后重试 (或保持 0，直到手动重置)

    /// <summary>
    /// 相机状态调和（核心自愈逻辑 - 修复版）
    /// 功能：校验相机物理连接、流状态，执行启动/停止/复位操作，确保状态一致性
    /// </summary>
    /// <param name="cam">待调和的相机设备</param>
    /// <param name="token">取消令牌</param>
    private async Task ReconcileAsync(BaseVideoSource cam, CancellationToken token)
    {
        // 1. 计算距离上次收到帧的时间（秒）
        long nowTick = Environment.TickCount64;

        // --- [新增] 分级冷冻判定逻辑 ---
        long elapsed = nowTick - cam.LastStartAttemptTick;

        // 如果是认证失败（致命），15分钟内不准动
        if (cam.IsAuthFailed && elapsed < FatalRetryMs) return;

        // 如果是普通网络问题，30秒内不准动
        if (!cam.IsAuthFailed && elapsed < NormalRetryMs) return;

        double secondsSinceLastFrame = (nowTick - cam.LastFrameTick) / 1000.0;

        // 2. 判定流是否正常：设备在线 + 15秒内有帧
        bool isFlowing = cam.IsActived && secondsSinceLastFrame < StreamAliveThresholdSeconds;

        // 3. 判定物理连接是否正常：流正常则直接判定在线；否则执行 Ping+TCP 探测
        //    (注意：如果哨兵已经更新了 Ping 状态，ProbeHardwareAsync 内部也可以优化为直接读取，
        //     但在 Coordinator 里保留主动探测作为双重保险也是合理的)
        bool isPhysicalOk = isFlowing ? true : await ProbeHardwareAsync(cam).ConfigureAwait(false);

        // 4. 状态调和决策：根据物理状态与设备状态的差异执行对应操作

        // 场景 A: 物理在线 + 设备离线 + 用户要求运行 -> 执行启动
        if (isPhysicalOk && !cam.IsActived && cam.IsRunning)
        {
            // 加登录锁防止冲突
            bool lockTaken = false;
            try
            {
                await _sdkLoginLock.WaitAsync(token).ConfigureAwait(false);
                lockTaken = true;

                // 双重校验：防止等待锁期间状态已变更
                if (!cam.IsActived)
                {
                    // 记录启动时刻，elapsed 将重新计时
                    cam.MarkStartAttempt();

                    try
                    {
                        await cam.StartAsync().ConfigureAwait(false);

                        // 成功后复位致命标记
                        cam.IsAuthFailed = false;
                    }
                    catch (CameraException ex) when (ex.ErrorCode == CameraErrorCode.InvalidCredentials)
                    {
                        // [新增] 识别到致命密码错误，打上标记，触发 15 分钟长冷冻
                        cam.IsAuthFailed = true;
                        _sysLog.Fatal($"[Coordinator] 设备 {cam.Id} 认证失败（密码错），已进入 15 分钟保护性冷冻以防封 IP。");
                    }
                    catch (Exception ex)
                    {
                        // 普通异常，维持普通冷冻（30秒）
                        _sysLog.Warning($"[Coordinator] 设备 {cam.Id} 启动失败: {ex.Message}");
                    }
                }
            }
            finally
            {
                if (lockTaken)
                {
                    _sdkLoginLock.Release();
                }
            }
        }
        // 场景 B: 物理离线 + 设备在线 -> 执行强制停止
        else if (!isPhysicalOk && cam.IsActived)
        {
            await cam.StopAsync().ConfigureAwait(false);
        }
        // 场景 C: 物理在线 + 设备在线 + 流中断 + 【用户要求运行】 -> 判定为僵死
        // 【关键修复】：增加了 && cam.IsRunning 判定，防止待机状态下被误复位
        else if (isPhysicalOk && cam.IsActived && !isFlowing && cam.IsRunning) // [cite: 504]
        {
            // Optimized: [修复无限重启] 增加“启动保护期”检查。
            // 原问题：相机刚 StartAsync 还在握手（例如第3秒），isFlowing 为 false，会导致立即被 Stop。
            // 新逻辑：只有当“启动已超过 15秒”且“依然没流”时，才判定为真正的僵死。

            // elapsed 是毫秒，StreamAliveThresholdSeconds 是秒，需要换算
            if (elapsed > StreamAliveThresholdSeconds * 1000)
            {
                _sysLog.Warning($"[Coordinator] [自愈] 设备 {cam.Id} 僵死（{secondsSinceLastFrame:F1}秒无帧），复位中...");
                await cam.StopAsync().ConfigureAwait(false);
            }
            else
            {
                _sysLog.Debug($"[Coordinator] 设备 {cam.Id} 启动握手中 ({elapsed}ms)，等待出流...");
            }
        }
    }

    #endregion

    #region --- 硬件探测 (Hardware Probing) ---

    /// <summary>
    /// 硬件连接探测：通过 Ping + TCP 双探测判定设备物理可达性
    /// </summary>
    /// <param name="cam">待探测的相机设备</param>
    /// <returns>物理可达返回 true，否则返回 false</returns>
    private async Task<bool> ProbeHardwareAsync(BaseVideoSource cam)
    {
        // 1. 优先执行 Ping 探测（快速判定网络连通性）
        try
        {
            using var ping = new Ping();
            PingReply reply = await ping.SendPingAsync(cam.Config.IpAddress, PingTimeoutMs).ConfigureAwait(false);
            if (reply.Status == IPStatus.Success)
            {
                return true;
            }
        }
        catch
        {
            // Ping 探测失败，执行 TCP 探测兜底
        }

        // 2. TCP 探测：尝试连接设备端口（更精准的服务可达性判定）
        try
        {
            using var tcpClient = new TcpClient();
            using var cts = new CancellationTokenSource(TcpTimeoutMs);
            await tcpClient.ConnectAsync(cam.Config.IpAddress, cam.Config.Port, cts.Token).ConfigureAwait(false);
            return true;
        }
        catch
        {
            // TCP 探测失败，判定为物理不可达
            return false;
        }
    }

    #endregion
}