using Health.Application.Notifications;
using Health.Domain.Enums;

namespace Health.WebApi.BackgroundServices;

/// <summary>
/// 健康录入提醒服务——每天 9:00 / 12:00 扫描所有用户，
/// 对未录入指标且开关开启的用户推送提醒。
/// 中国时区 UTC+8。
/// </summary>
public sealed class HealthRecordReminderService(
    IServiceScopeFactory scopeFactory,
    ILogger<HealthRecordReminderService> logger) : BackgroundService
{
    private readonly IServiceScopeFactory _scopeFactory = scopeFactory;
    private readonly ILogger<HealthRecordReminderService> _logger = logger;

    // 北京时间 9:00 和 12:00 触发
    private static readonly int[] ReminderHoursCst = [9, 12];

    private DateTime _lastFired = DateTime.MinValue;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("健康录入提醒服务已启动");
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                var nowCst = DateTime.UtcNow.AddHours(8);
                // 当前小时是否在触发列表，且今天该小时还没触发过
                if (ReminderHoursCst.Contains(nowCst.Hour)
                    && (_lastFired.Date != nowCst.Date || _lastFired.Hour != nowCst.Hour))
                {
                    await ScanAndPushAsync(nowCst, stoppingToken);
                    _lastFired = nowCst;
                }
            }
            catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { break; }
            catch (Exception ex) { _logger.LogError(ex, "健康录入提醒扫描异常"); }

            await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
        }
    }

    private async Task ScanAndPushAsync(DateTime nowCst, CancellationToken ct)
    {
        using var scope = _scopeFactory.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var producer = scope.ServiceProvider.GetRequiredService<IUserNotificationProducer>();

        var todayStart = nowCst.Date.AddHours(-8); // 北京 0:00 转 UTC
        var todayEnd = todayStart.AddDays(1);

        // 取所有启用了健康录入提醒的用户偏好
        var prefs = await db.NotificationPreferences
            .Where(p => p.HealthRecordReminder)
            .ToListAsync(ct);

        foreach (var pref in prefs)
        {
            // 该用户当天已录入的指标
            var recordedTypes = await db.HealthRecords
                .Where(r => r.UserId == pref.UserId && r.RecordedAt >= todayStart && r.RecordedAt < todayEnd)
                .Select(r => r.MetricType)
                .Distinct()
                .ToListAsync(ct);

            // 哪些指标启用开关但当天没录入
            var missing = new List<(HealthMetricType type, bool enabled, string label)>
            {
                (HealthMetricType.BloodPressure, pref.HealthRecordReminderBloodPressure, "血压"),
                (HealthMetricType.HeartRate, pref.HealthRecordReminderHeartRate, "心率"),
                (HealthMetricType.Glucose, pref.HealthRecordReminderGlucose, "血糖"),
                (HealthMetricType.SpO2, pref.HealthRecordReminderSpO2, "血氧"),
                (HealthMetricType.Weight, pref.HealthRecordReminderWeight, "体重"),
            };

            var missingLabels = missing
                .Where(m => m.enabled && !recordedTypes.Contains(m.type))
                .Select(m => m.label)
                .ToList();

            if (missingLabels.Count == 0) continue;

            // 每天每个时段每个用户只推一次——SourceTaskId 用 userId + 日期 + 小时
            var sourceId = MakeSourceId(pref.UserId, nowCst);
            var summary = string.Join("、", missingLabels);

            await producer.EnqueueAsync(pref.UserId, sourceId, new NotificationMessage(
                Type: "health_record_reminder",
                Title: nowCst.Hour < 12 ? "早安，记得录入今日健康数据" : "下午好，今日健康数据还未录入",
                Message: $"建议录入：{summary}",
                Severity: "info",
                ActionType: "health",
                ActionTargetId: null), ct);
        }
    }

    private static Guid MakeSourceId(Guid userId, DateTime nowCst)
    {
        // 通过哈希构造稳定的 Guid，保证同一用户同一时段不重复
        var key = $"hrr|{userId:N}|{nowCst:yyyyMMddHH}";
        var bytes = System.Security.Cryptography.MD5.HashData(System.Text.Encoding.UTF8.GetBytes(key));
        return new Guid(bytes);
    }
}
