using AutoNotificatPhone.Controllers;
using Common;
using Microsoft.Extensions.Configuration;
using NLog;
using Npgsql;
using System.Diagnostics;

namespace AutoNotificatPhone.Models
{
    /// <summary>
    /// 定时后台服务：
    /// 1) 每分钟固定时刻执行巡检
    /// 2) 执行整点/定时通知
    /// 3) 执行 Redis 指标告警
    /// 4) 执行 PostgreSQL 心跳检查告警
    /// </summary>
    public class TimerClass : BackgroundService
    {
        // NLog 记录器
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

        // 告警通知接收手机号
        private static readonly string Mobile1 = "13509214696";
        private static readonly string Mobile2 = "16620970520";

        // 每日定时任务触发小时（北京时间）
        private static readonly HashSet<int> DailyTaskHours = [10, 15, 22];

        // 每分钟在第 30 秒触发一次巡检
        private const int RunSecond = 30;
        // 主循环异常后的重试等待时间（秒）
        private const int RetryDelaySeconds = 10;

        // 默认短信/电话任务过期时间（秒）
        private const int SmsDeadlineSeconds = 1800;
        private const int CallDeadlineSeconds = 900;
        // 扩展短信/电话任务过期时间（秒）
        private const int ExtendedSmsDeadlineSeconds = 3600;
        private const int ExtendedCallDeadlineSeconds = 1800;

        // Kafka 心跳超时阈值（分钟）
        private const int KafkaStaleMinutes = 5;
        // 数据库连接失败累计到 N 次才触发一次告警，避免告警风暴
        private const int KafkaDbAlertTriggerCount = 8;

        // 接收包“低值”判定阈值
        private const int RecvPackageLowThreshold = 70000;

        // 用于防止同一整点任务重复执行
        private readonly Dictionary<DateTime, bool> _executedTasks = new();
        // 复用 API 控制器发送短信/电话任务
        private readonly CallAndMsgController _callAndMsgController = new();
        // 从 appsettings 读取 Postgres 配置
        private readonly IConfiguration _configuration;

        // Kafka 数据库连接失败计数器
        private int _kafkaDbConnectionAlertCount;

        /// <summary>
        /// 构造函数，注入配置对象。
        /// </summary>
        /// <param name="configuration">应用配置（用于读取 Postgres 节点）</param>
        public TimerClass(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        /// <summary>
        /// 后台服务主循环。
        /// </summary>
        /// <param name="cancellationToken">服务取消令牌</param>
        protected override async Task ExecuteAsync(CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                try
                {
                    // 等待到下一次固定执行时刻（每分钟第 RunSecond 秒）
                    await DelayUntilNextRunAsync(cancellationToken);

                    // 检查电话机进程在线状态并写日志
                    LogPhoneStatus(CheckPhoneIsOnline());

                    // 执行整点/每日通知任务
                    RunHourlyNotificationTask();

                    // 执行各项系统检查任务
                    CheckCpuThreshold();
                    CheckRcuOnline();
                    CheckTotalSendPackage();
                    CheckTotalRecvPackage();
                    await CheckKafkaHeartbeatAsync();
                }
                catch (TaskCanceledException)
                {
                    // 服务停止时会进入这里
                    Logger.Error("任务被取消");
                    break;
                }
                catch (Exception ex)
                {
                    // 主循环兜底异常，稍后重试
                    Logger.Error($"主循环发生错误: {ex.Message}");
                    await Task.Delay(TimeSpan.FromSeconds(RetryDelaySeconds), cancellationToken);
                }
            }
        }

        /// <summary>
        /// 等待到下一次固定执行时间。
        /// </summary>
        /// <param name="cancellationToken">取消令牌</param>
        private async Task DelayUntilNextRunAsync(CancellationToken cancellationToken)
        {
            var now = DateTime.UtcNow;
            var nextRunTime = CalculateNextRunTime(now);
            var delayTime = nextRunTime - now;
            // 到点前阻塞等待
            await Task.Delay(delayTime, cancellationToken);
        }

        /// <summary>
        /// 计算下一次执行时间点（每分钟第 RunSecond 秒）。
        /// </summary>
        /// <param name="now">当前 UTC 时间</param>
        /// <returns>下一次执行时间</returns>
        private static DateTime CalculateNextRunTime(DateTime now)
        {
            var nextRunTime = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, RunSecond);
            // 如果当前秒已过触发点，则顺延到下一分钟
            return now.Second >= RunSecond ? nextRunTime.AddMinutes(1) : nextRunTime;
        }

        /// <summary>
        /// 输出电话机在线状态日志。
        /// </summary>
        /// <param name="isOnline">是否在线</param>
        private static void LogPhoneStatus(bool isOnline)
        {
            Logger.Error(isOnline
                ? "电话机在线，开始判断！+++++str+++++"
                : "电话机不在线，下面内容可能不会执行！+++++err+++++");
        }

        /// <summary>
        /// 通过本机进程名判断电话机程序是否运行。
        /// </summary>
        /// <returns>在线返回 true，否则 false</returns>
        private static bool CheckPhoneIsOnline()
        {
            try
            {
                // 约定进程名为 Telephone
                return Process.GetProcessesByName("Telephone").Length > 0;
            }
            catch (Exception ex)
            {
                Logger.Error($"电话机进程检查失败: {ex.Message}");
                return false;
            }
        }

        /// <summary>
        /// 整点通知任务调度（北京时间）：
        /// - 非整点直接返回
        /// - 同一整点只执行一次
        /// - 10/15/22 点执行每日任务，其余整点执行整点短信
        /// </summary>
        private void RunHourlyNotificationTask()
        {
            // 当前北京时间
            var beijingTime = DateTime.UtcNow.AddHours(8);

            // 仅整点触发
            if (beijingTime.Minute != 0)
            {
                return;
            }

            // 当前整点键，用于去重
            var hourlyKey = new DateTime(beijingTime.Year, beijingTime.Month, beijingTime.Day, beijingTime.Hour, 0, 0);
            if (_executedTasks.ContainsKey(hourlyKey))
            {
                // 避免重复执行
                return;
            }

            Logger.Error($"准备执行整点短信任务 - 时间点: {hourlyKey:yyyy-MM-dd HH:mm}");

            // 每日固定时点执行“每日任务”，否则执行“整点短信”
            if (DailyTaskHours.Contains(beijingTime.Hour))
            {
                ExecuteDailyTask(beijingTime);
            }
            else
            {
                SendHourlySms(beijingTime);
            }

            // 标记当前整点已执行
            _executedTasks[hourlyKey] = true;
            // 清理历史日期记录
            CleanupOldTasks(beijingTime);
        }

        /// <summary>
        /// 清理前一天及更早的整点执行记录。
        /// </summary>
        /// <param name="currentTime">当前时间（北京时间）</param>
        private void CleanupOldTasks(DateTime currentTime)
        {
            var keysToRemove = _executedTasks.Keys.Where(key => key.Date < currentTime.Date).ToList();
            foreach (var key in keysToRemove)
            {
                _executedTasks.Remove(key);
            }
        }

        /// <summary>
        /// 发送整点短信。
        /// </summary>
        /// <param name="beijingTime">当前北京时间</param>
        private void SendHourlySms(DateTime beijingTime)
        {
            try
            {
                // 生成展示时间文本
                var currentTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
                var dateTimeStr = $"{beijingTime.Month}月{beijingTime.Day}日{beijingTime.Hour}点";
                var smsContent = $"[BLV运维提示] 整点系统状态报告。当前时间：{dateTimeStr}";

                // 仅发送短信到 Mobile1
                var request = CreateSmsRequest(
                    type: "2",
                    deadline: currentTimestamp + SmsDeadlineSeconds,
                    phone: Mobile1,
                    caller: "整点报告",
                    content: smsContent);

                // 投递短信任务
                _callAndMsgController.SendToPhone(request);
            }
            catch (Exception ex)
            {
                Logger.Error($"发送整点短信时出错：{ex.Message}");
            }
        }

        /// <summary>
        /// 执行每日定时通知（短信 + 电话）。
        /// </summary>
        /// <param name="beijingTime">当前北京时间</param>
        private void ExecuteDailyTask(DateTime beijingTime)
        {
            try
            {
                var currentTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
                var dateTimeStr = $"{beijingTime.Month}月{beijingTime.Day}日{beijingTime.Hour}点";
                var smsContent = $"[BLV运维提示] 每日定时通知。当前时间为：{dateTimeStr}";
                var callContent = $"BLV运维提示 每日定时通知 当前时间为 {dateTimeStr}";

                // 两路短信 + 一路电话
                var smsRequest1 = CreateSmsRequest("2", currentTimestamp + SmsDeadlineSeconds, Mobile1, "每日定时通知", smsContent);
                var smsRequest2 = CreateSmsRequest("2", currentTimestamp + SmsDeadlineSeconds, Mobile2, "每日定时通知", smsContent);
                var callRequest = CreateSmsRequest("1", currentTimestamp + CallDeadlineSeconds, Mobile1, "每日定时通知", callContent);

                // 执行发送并检查结果
                var smsResult1 = _callAndMsgController.SendToPhone(smsRequest1);
                var smsResult2 = _callAndMsgController.SendToPhone(smsRequest2);
                var callResult = _callAndMsgController.SendToPhone(callRequest);

                if (!smsResult1.isok || !smsResult2.isok || !callResult.isok)
                {
                    Logger.Error($"发送每日定时通知失败：短信1={smsResult1.message} 短信2={smsResult2.message} 电话={callResult.message}");
                }
            }
            catch (Exception ex)
            {
                Logger.Error($"执行每日任务时出错：{ex.Message}");
            }
        }

        /// <summary>
        /// CPU 阈值检查：
        /// 1) 先判断监控程序是否失联
        /// 2) 再判断 CPU 指标是否超过阈值
        /// </summary>
        private void CheckCpuThreshold()
        {
            try
            {
                // 监控程序最后上报时间（来自 Redis）
                var detectTimeString = CSRedisCacheHelper.redis1.Get<string>("UDPPackage_DetectTime");
                if (!string.IsNullOrEmpty(detectTimeString) &&
                    DateTime.TryParse(detectTimeString, out var detectTime) &&
                    (DateTime.UtcNow - detectTime).TotalMinutes > 10)
                {
                    // 超过 10 分钟未更新，触发监控失联告警
                    ExecuteMonitorUnavailableAlert(detectTime);
                    return;
                }

                // 拉取 CPU 指标
                var cpuMax = CSRedisCacheHelper.redis1.Get<string>("UDPPackage_CPUMax");
                var cpuAvg = CSRedisCacheHelper.redis1.Get<string>("UDPPackage_CPUAvg");

                var cpuMaxValues = ParseCsvToIntList(cpuMax);
                var cpuAvgValues = ParseCsvToIntList(cpuAvg);

                // 规则：平均 CPU >= 80 的点达到 6 个触发告警
                if (CheckThreshold(cpuAvgValues, threshold: 80, requiredCount: 6))
                {
                    var cpuMinValues = ParseCsvToIntList(CSRedisCacheHelper.redis1.Get<string>("UDPPackage_CPUMin"));
                    ExecuteCpuAlert(cpuMaxValues, cpuMinValues, cpuAvgValues);
                }
            }
            catch (Exception ex)
            {
                Logger.Error($"CPU阈值检查错误: {ex.Message}");
            }
        }

        /// <summary>
        /// 检查 RCU 在线数量。
        /// </summary>
        private void CheckRcuOnline()
        {
            CheckRedisValue(
                redisKey: "RCUOnLine",
                baselineCount: 8,
                thresholdRatio: 0.8,
                alertAction: ExecuteRcuOnlineAlert,
                logPrefix: "RCU主机的在线数量");
        }

        /// <summary>
        /// 检查 RCU 总发送包数量。
        /// </summary>
        private void CheckTotalSendPackage()
        {
            CheckRedisValue(
                redisKey: "UDPPackage_TotalSendPackage",
                baselineCount: 8,
                thresholdRatio: 0.6,
                alertAction: ExecuteTotalSendPackageAlert,
                logPrefix: "RCU主机的通讯数量");
        }

        /// <summary>
        /// 检查 RCU 总接收包数量。
        /// 除了通用阈值逻辑外，还增加“最后 3 个值都低于固定阈值”的快速告警。
        /// </summary>
        private void CheckTotalRecvPackage()
        {
            try
            {
                // 获取接收包时序数据
                var valueString = CSRedisCacheHelper.redis1.Get<string>("UDPPackage_TotalRecvPackage");
                if (string.IsNullOrEmpty(valueString))
                {
                    return;
                }

                var values = ParseCsvToIntList(valueString);
                if (values.Count < 10)
                {
                    // 数据点不足，无法按规则判定
                    return;
                }

                // 特殊规则：最后 3 个点都很低，立即告警
                if (values.Count >= 3 && values[^3] < RecvPackageLowThreshold && values[^2] < RecvPackageLowThreshold && values[^1] < RecvPackageLowThreshold)
                {
                    ExecuteTotalRecvPackageAlert([values[^3], values[^2], values[^1]]);
                    return;
                }

                // 回退到通用阈值规则
                CheckRedisValue(
                    redisKey: "UDPPackage_TotalRecvPackage",
                    baselineCount: 8,
                    thresholdRatio: 0.75,
                    alertAction: ExecuteTotalRecvPackageAlert,
                    logPrefix: "RCU主机的通讯数量");
            }
            catch (Exception ex)
            {
                Logger.Error($"总接收包数量检查错误: {ex.Message}");
            }
        }

        /// <summary>
        /// 通用 Redis 时序指标检查：
        /// - 以前 baselineCount 个点的平均值作为基线
        /// - 计算阈值（平均值 * thresholdRatio）
        /// - 若后续两个点都低于阈值则触发告警
        /// </summary>
        /// <param name="redisKey">Redis 键</param>
        /// <param name="baselineCount">基线样本数量</param>
        /// <param name="thresholdRatio">阈值比例</param>
        /// <param name="alertAction">告警动作</param>
        /// <param name="logPrefix">日志前缀</param>
        private void CheckRedisValue(string redisKey, int baselineCount, double thresholdRatio, Action<List<int>> alertAction, string logPrefix)
        {
            try
            {
                // 从 Redis 读取 CSV 字符串
                var valueString = CSRedisCacheHelper.redis1.Get<string>(redisKey);
                if (string.IsNullOrEmpty(valueString))
                {
                    return;
                }

                var values = ParseCsvToIntList(valueString);
                if (values.Count < 10)
                {
                    return;
                }

                // 计算阈值
                var average = values.Take(baselineCount).Average();
                var threshold = average * thresholdRatio;

                // 后续两个点均低于阈值才触发
                if (values[baselineCount] < threshold && values[baselineCount + 1] < threshold)
                {
                    alertAction(values);
                }
            }
            catch (Exception ex)
            {
                Logger.Error($"{logPrefix}检查错误: {ex.Message}");
            }
        }

        /// <summary>
        /// RCU 在线数量告警。
        /// </summary>
        private void ExecuteRcuOnlineAlert(List<int> values)
        {
            SendAlert(
                smsContent: $"[BLV运维提示] RCU主机在线数量低于正常值，请立即检查。数据：{string.Join(",", values)}",
                callContent: "BLV运维提示 RCU主机在线数量低于正常值 请立即检查",
                alertType: "RCU-在线数量警报");
        }

        /// <summary>
        /// RCU 发送数量告警。
        /// </summary>
        private void ExecuteTotalSendPackageAlert(List<int> values)
        {
            SendAlert(
                smsContent: $"[BLV运维提示] RCU发送数量低于预期值，请立即检查。数据：{string.Join(",", values)}",
                callContent: "BLV运维提示 RCU发送数量低于预期值 请立即检查",
                alertType: "RCU-通讯数量警报");
        }

        /// <summary>
        /// RCU 接收数量告警。
        /// </summary>
        private void ExecuteTotalRecvPackageAlert(List<int> values)
        {
            SendAlert(
                smsContent: $"[BLV运维提示] RCU接收数量低于预期值，请立即检查。数据：{string.Join(",", values)}",
                callContent: "BLV运维提示 RCU接收数量低于预期值 请立即检查",
                alertType: "RCU-通讯数量警报");
        }

        /// <summary>
        /// CPU 告警。
        /// </summary>
        private void ExecuteCpuAlert(List<int> cpuMax, List<int> cpuMin, List<int> cpuAvg)
        {
            // 拼接 CPU 指标明细
            var dataString = $"AVG:{string.Join(",", cpuAvg)},MAX:{string.Join(",", cpuMax)},MIN:{string.Join(",", cpuMin)}";
            SendAlert(
                smsContent: $"[BLV运维提示] RCU服务器的CPU使用率告警。{dataString}",
                callContent: "BLV运维提示 RCU服务器的CPU使用率告警 请立即检查",
                alertType: "RCU-CPU警报");
        }

        /// <summary>
        /// 监控程序失联告警。
        /// </summary>
        /// <param name="detectTime">最后检测时间（当前版本仅用于语义传参）</param>
        private void ExecuteMonitorUnavailableAlert(DateTime detectTime)
        {
            SendAlert(
                smsContent: "[BLV运维提示] RCU服务器的监控程序无法访问，请立即检查。",
                callContent: "BLV运维提示 RCU服务器的监控程序无法访问 请立即检查",
                alertType: "RCU-监控程序警报",
                extendedDeadline: true);
        }

        /// <summary>
        /// 检查 PostgreSQL 心跳表，判断 Kafka 入库是否活跃。
        /// </summary>
        private async Task CheckKafkaHeartbeatAsync()
        {
            try
            {
                // 从配置构建连接串
                var connectionString = BuildPostgresConnectionString();
                if (string.IsNullOrWhiteSpace(connectionString))
                {
                    Logger.Error("Postgres配置缺失，无法检查Kafka入库心跳");
                    // 配置缺失等价于连接失败告警路径
                    ExecuteKafkaDbConnectionAlert();
                    return;
                }

                // 建立数据库连接
                await using var connection = new NpgsqlConnection(connectionString);
                await connection.OpenAsync();

                // 查询最近数据中的最新 write_ts_ms
                const string sql = @"SELECT write_ts_ms
                                    FROM (
                                        SELECT write_ts_ms
                                        FROM heartbeat.heartbeat_events
                                        ORDER BY ts_ms DESC
                                        LIMIT 3000
                                    ) AS recent_events
                                    ORDER BY write_ts_ms DESC
                                    LIMIT 1;";

                await using var command = new NpgsqlCommand(sql, connection);
                var result = await command.ExecuteScalarAsync();

                // 空结果按数据库异常路径处理
                if (result == null || result == DBNull.Value)
                {
                    Logger.Error("Kafka入库心跳ts_ms查询结果为空");
                    ExecuteKafkaDbConnectionAlert();
                    return;
                }

                // 解析时间戳（毫秒）
                if (!long.TryParse(result.ToString(), out var lastTsMs))
                {
                    Logger.Error("Kafka入库心跳ts_ms解析失败");
                    ExecuteKafkaDbConnectionAlert();
                    return;
                }

                // 按“当前时间 - 最新入库时间”判断是否超时
                var nowMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
                if (nowMs - lastTsMs > TimeSpan.FromMinutes(KafkaStaleMinutes).TotalMilliseconds)
                {
                    Logger.Error($"Kafka入库心跳超过{KafkaStaleMinutes}分钟未更新");
                    ExecuteKafkaInactiveAlert();
                }
            }
            catch (Exception ex)
            {
                Logger.Error($"Kafka入库心跳检查错误: {ex.Message}");
                ExecuteKafkaDbConnectionAlert();
            }
        }

        /// <summary>
        /// 从配置读取 Postgres 参数并生成连接字符串。
        /// </summary>
        /// <returns>可用连接串；若关键配置缺失则返回 null</returns>
        private string? BuildPostgresConnectionString()
        {
            // 约定配置节点：Postgres
            var section = _configuration.GetSection("Postgres");

            var host = section["Host"];
            var portString = section["Port"];
            var database = section["Database"];
            var username = section["User"];
            var password = section["Password"];
            var maxConnectionsString = section["MaxConnections"];
            var idleTimeoutMsString = section["IdleTimeoutMs"];

            // 必填项校验
            if (string.IsNullOrWhiteSpace(host) ||
                string.IsNullOrWhiteSpace(portString) ||
                string.IsNullOrWhiteSpace(database) ||
                string.IsNullOrWhiteSpace(username) ||
                string.IsNullOrWhiteSpace(password))
            {
                return null;
            }

            // 端口格式校验
            if (!int.TryParse(portString, out var port))
            {
                return null;
            }

            // 构建基础连接串
            var builder = new NpgsqlConnectionStringBuilder
            {
                Host = host,
                Port = port,
                Database = database,
                Username = username,
                Password = password
            };

            // 连接池最大连接数（可选）
            if (int.TryParse(maxConnectionsString, out var maxConnections) && maxConnections > 0)
            {
                builder.MaxPoolSize = maxConnections;
            }

            // 空闲连接生命周期（ms -> s）
            if (int.TryParse(idleTimeoutMsString, out var idleTimeoutMs) && idleTimeoutMs > 0)
            {
                builder.ConnectionIdleLifetime = Math.Max(1, idleTimeoutMs / 1000);
            }

            return builder.ConnectionString;
        }

        /// <summary>
        /// Kafka 入库停滞告警。
        /// </summary>
        private void ExecuteKafkaInactiveAlert()
        {
            SendAlert(
                smsContent: "[BLV运维提示] BLS数据库3分钟内入库数据为0。",
                callContent: "BLV运维提示 BLS数据库3分钟内入库数据为0",
                alertType: "BLS-数据库入库警报",
                extendedDeadline: true);
        }

        /// <summary>
        /// Kafka 数据库连接异常告警（带计数节流）。
        /// </summary>
        private void ExecuteKafkaDbConnectionAlert()
        {
            // 每次失败计数 +1，累计到阈值再告警
            _kafkaDbConnectionAlertCount++;
            if (_kafkaDbConnectionAlertCount < KafkaDbAlertTriggerCount)
            {
                return;
            }

            // 触发一次后清零重新计数
            SendAlert(
                smsContent: "[BLV运维提示] 数据库连接失败！",
                callContent: "[BLV运维提示] 数据库连接失败",
                alertType: "BLS-数据库连接警报",
                extendedDeadline: true);

            _kafkaDbConnectionAlertCount = 0;
        }

        /// <summary>
        /// 将逗号分隔字符串解析为整型列表；解析失败项按 0 处理。
        /// </summary>
        /// <param name="valueString">CSV 字符串</param>
        /// <returns>整型列表</returns>
        private static List<int> ParseCsvToIntList(string valueString)
        {
            if (string.IsNullOrEmpty(valueString))
            {
                return [];
            }

            return valueString
                .Split(',')
                .Select(item => int.TryParse(item, out var number) ? number : 0)
                .ToList();
        }

        /// <summary>
        /// 判断列表中是否至少有 requiredCount 个值达到 threshold。
        /// </summary>
        /// <param name="values">待检查值集合</param>
        /// <param name="threshold">阈值</param>
        /// <param name="requiredCount">最少命中数量</param>
        /// <returns>满足返回 true</returns>
        private static bool CheckThreshold(List<int> values, int threshold, int requiredCount)
        {
            return values.Count >= requiredCount && values.Count(v => v >= threshold) >= requiredCount;
        }

        /// <summary>
        /// 统一告警发送入口（两条短信 + 一通电话）。
        /// </summary>
        /// <param name="smsContent">短信内容</param>
        /// <param name="callContent">电话播报内容</param>
        /// <param name="alertType">告警类型（用于 caller 与日志）</param>
        /// <param name="extendedDeadline">是否使用扩展过期时间</param>
        private void SendAlert(string smsContent, string callContent, string alertType, bool extendedDeadline = false)
        {
            // 计算任务过期时间
            var nowSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
            var smsDeadline = nowSeconds + (extendedDeadline ? ExtendedSmsDeadlineSeconds : SmsDeadlineSeconds);
            var callDeadline = nowSeconds + (extendedDeadline ? ExtendedCallDeadlineSeconds : CallDeadlineSeconds);

            // 构建请求：两路短信（Mobile1/2）+ 一路电话（Mobile1）
            var smsRequest1 = CreateSmsRequest("2", smsDeadline, Mobile1, alertType, smsContent);
            var smsRequest2 = CreateSmsRequest("2", smsDeadline, Mobile2, alertType, smsContent);
            var callRequest = CreateSmsRequest("1", callDeadline, Mobile1, alertType, callContent);

            // 调用 API 投递任务
            var smsResult1 = _callAndMsgController.SendToPhone(smsRequest1);
            var smsResult2 = _callAndMsgController.SendToPhone(smsRequest2);
            var callResult = _callAndMsgController.SendToPhone(callRequest);

            // 任意一路失败都记录错误日志
            if (!smsResult1.isok || !smsResult2.isok || !callResult.isok)
            {
                Logger.Error($"发送{alertType}通知失败: 短信1={smsResult1.message} 短信2={smsResult2.message} 电话={callResult.message}");
            }
        }

        /// <summary>
        /// 创建短信/电话请求对象。
        /// </summary>
        /// <param name="type">1=电话，2=短信</param>
        /// <param name="deadline">截止时间（Unix 秒）</param>
        /// <param name="phone">目标手机号</param>
        /// <param name="caller">任务标识/来电名称</param>
        /// <param name="content">内容</param>
        /// <returns>SmsRequest 对象</returns>
        private static SmsRequest CreateSmsRequest(string type, long deadline, string phone, string caller, string content)
        {
            return new SmsRequest
            {
                // 业务类型（1 电话 / 2 短信）
                Type = type,
                // 截止时间
                DeadLine = deadline,
                // 创建时间
                StartingPoint = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
                // 目标号码
                PhoneNumber = phone,
                // 任务标识
                CallerName = caller,
                // 消息内容
                Content = content
            };
        }
    }
}
