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

namespace Health.Infrastructure.Notifications;

public sealed class EfReminderCatchUpService(AppDbContext db) : IReminderCatchUpService
{
    private readonly AppDbContext _db = db;

    public async Task<ReminderCatchUpResult> CheckDueAsync(Guid userId, DateTime utcNow, CancellationToken ct)
    {
        var pref = await GetPreferenceAsync(userId, ct);
        if (!pref.PushEnabled) return new ReminderCatchUpResult(0);

        var beijingNow = utcNow.AddHours(8);
        var today = DateOnly.FromDateTime(beijingNow);
        var todayStartUtc = beijingNow.Date.AddHours(-8);
        var todayEndUtc = todayStartUtc.AddDays(1);
        var created = 0;

        created += await CreateHealthRecordReminderAsync(userId, pref, beijingNow, todayStartUtc, todayEndUtc, ct);
        created += await CreateMedicationRemindersAsync(userId, pref, beijingNow, today, todayStartUtc, todayEndUtc, ct);
        created += await CreateExerciseRemindersAsync(userId, pref, beijingNow, today, ct);
        created += await CreateFollowUpRemindersAsync(userId, pref, beijingNow, ct);

        if (created > 0)
            await _db.SaveChangesAsync(ct);

        return new ReminderCatchUpResult(created);
    }

    private async Task<NotificationPreference> GetPreferenceAsync(Guid userId, CancellationToken ct)
    {
        var pref = await _db.NotificationPreferences.FirstOrDefaultAsync(x => x.UserId == userId, ct);
        if (pref != null) return pref;

        pref = new NotificationPreference { Id = Guid.NewGuid(), UserId = userId };
        await _db.NotificationPreferences.AddAsync(pref, ct);
        return pref;
    }

    private async Task<int> CreateHealthRecordReminderAsync(
        Guid userId,
        NotificationPreference pref,
        DateTime beijingNow,
        DateTime todayStartUtc,
        DateTime todayEndUtc,
        CancellationToken ct)
    {
        if (!pref.HealthRecordReminder || IsInDnd(pref, beijingNow)) return 0;
        var window = HealthRecordWindow(beijingNow);
        if (window == null) return 0;

        var recordedTypes = await _db.HealthRecords.AsNoTracking()
            .Where(r => r.UserId == userId && r.RecordedAt >= todayStartUtc && r.RecordedAt < todayEndUtc)
            .Select(r => r.MetricType)
            .Distinct()
            .ToListAsync(ct);

        var missing = EnabledHealthMetrics(pref)
            .Where(x => !recordedTypes.Contains(x.Type))
            .ToList();
        if (missing.Count == 0) return 0;

        var date = DateOnly.FromDateTime(beijingNow);
        var sourceId = StableGuid($"health_record|{userId:N}|{date:yyyyMMdd}|{window}");
        return await AddNotificationIfMissingAsync(
            userId,
            sourceId,
            "health_record_reminder",
            window == "evening" ? "今日健康指标还未完成" : "记得录入今日健康指标",
            $"建议录入：{string.Join("、", missing.Select(x => x.Label))}",
            "info",
            "health",
            null,
            ct);
    }

    private async Task<int> CreateMedicationRemindersAsync(
        Guid userId,
        NotificationPreference pref,
        DateTime beijingNow,
        DateOnly today,
        DateTime todayStartUtc,
        DateTime todayEndUtc,
        CancellationToken ct)
    {
        if (!pref.MedicationReminder || IsInDnd(pref, beijingNow)) return 0;
        var now = TimeOnly.FromDateTime(beijingNow);
        var earliest = now.AddHours(-4);
        var medications = await _db.Medications.AsNoTracking()
            .Where(m => m.UserId == userId && m.IsActive)
            .ToListAsync(ct);
        var created = 0;

        foreach (var medication in medications.Where(m => IsMedicationActiveOn(m, today) && ShouldDoseToday(m, today)))
        {
            foreach (var scheduledTime in medication.TimeOfDay.Where(t => IsInTimeWindow(t, earliest, now)))
            {
                var logged = await _db.MedicationLogs.AsNoTracking().AnyAsync(l =>
                    l.UserId == userId &&
                    l.MedicationId == medication.Id &&
                    l.ScheduledTime == scheduledTime &&
                    l.CreatedAt >= todayStartUtc &&
                    l.CreatedAt < todayEndUtc, ct);
                if (logged) continue;

                var sourceId = StableGuid($"medication|{userId:N}|{medication.Id:N}|{today:yyyyMMdd}|{scheduledTime:HHmm}");
                var details = string.Join(" ", new[] { medication.Dosage, scheduledTime.ToString("HH:mm") }
                    .Where(x => !string.IsNullOrWhiteSpace(x)));
                created += await AddNotificationIfMissingAsync(
                    userId,
                    sourceId,
                    "MedicationReminder",
                    "用药时间到了",
                    $"请按计划服用{medication.Name}{(details.Length > 0 ? $"（{details}）" : "")}，服用后记得打卡。",
                    "info",
                    "medication",
                    medication.Id.ToString(),
                    ct);
            }
        }

        return created;
    }

    private async Task<int> CreateExerciseRemindersAsync(
        Guid userId,
        NotificationPreference pref,
        DateTime beijingNow,
        DateOnly today,
        CancellationToken ct)
    {
        if (IsInDnd(pref, beijingNow)) return 0;
        var now = TimeOnly.FromDateTime(beijingNow);
        var dueItems = await _db.ExercisePlanItems.AsNoTracking()
            .Include(x => x.Plan)
            .Where(x => x.Plan.UserId == userId &&
                x.ScheduledDate == today &&
                !x.IsCompleted &&
                !x.IsRestDay &&
                x.Plan.ReminderTime <= now)
            .ToListAsync(ct);
        var created = 0;

        foreach (var item in dueItems)
        {
            var sourceId = StableGuid($"exercise|{userId:N}|{item.Id:N}|{today:yyyyMMdd}");
            created += await AddNotificationIfMissingAsync(
                userId,
                sourceId,
                "ExerciseReminder",
                "今日运动计划",
                $"今天安排了{item.ExerciseType} {item.DurationMinutes} 分钟，完成后记得打卡。",
                "info",
                "exercise",
                item.PlanId.ToString(),
                ct);
        }

        return created;
    }

    private async Task<int> CreateFollowUpRemindersAsync(
        Guid userId,
        NotificationPreference pref,
        DateTime beijingNow,
        CancellationToken ct)
    {
        if (!pref.FollowUpReminder || IsInDnd(pref, beijingNow)) return 0;
        var nowUtc = beijingNow.AddHours(-8);
        var upcomingEndUtc = nowUtc.AddHours(24);
        var followUps = await _db.FollowUps.AsNoTracking()
            .Where(x => x.UserId == userId && x.ScheduledAt >= nowUtc && x.ScheduledAt <= upcomingEndUtc)
            .ToListAsync(ct);
        var created = 0;

        foreach (var followUp in followUps)
        {
            var sourceId = StableGuid($"followup|{userId:N}|{followUp.Id:N}|24h");
            created += await AddNotificationIfMissingAsync(
                userId,
                sourceId,
                "follow_up_reminder",
                "复查提醒",
                $"{followUp.Title} 即将到期，请提前安排时间。",
                "info",
                "followup",
                followUp.Id.ToString(),
                ct);
        }

        return created;
    }

    private async Task<int> AddNotificationIfMissingAsync(
        Guid userId,
        Guid sourceId,
        string type,
        string title,
        string message,
        string severity,
        string? actionType,
        string? actionTargetId,
        CancellationToken ct)
    {
        if (await _db.UserNotifications.AsNoTracking().AnyAsync(x => x.SourceId == sourceId, ct))
            return 0;

        await _db.UserNotifications.AddAsync(new UserNotification
        {
            Id = Guid.NewGuid(),
            UserId = userId,
            SourceId = sourceId,
            Type = type,
            Title = title,
            Message = message,
            Severity = severity,
            ActionType = actionType,
            ActionTargetId = actionTargetId,
            CreatedAt = DateTime.UtcNow,
        }, ct);
        return 1;
    }

    private static string? HealthRecordWindow(DateTime beijingNow)
    {
        var minutes = beijingNow.Hour * 60 + beijingNow.Minute;
        if (minutes is >= 7 * 60 and <= 10 * 60 + 30) return "morning";
        if (minutes is >= 11 * 60 + 30 and <= 14 * 60) return "noon";
        if (minutes is >= 18 * 60 and <= 21 * 60 + 30) return "evening";
        return null;
    }

    private static IReadOnlyList<(HealthMetricType Type, string Label)> EnabledHealthMetrics(NotificationPreference pref)
    {
        var result = new List<(HealthMetricType, string)>();
        if (pref.HealthRecordReminderBloodPressure) result.Add((HealthMetricType.BloodPressure, "血压"));
        if (pref.HealthRecordReminderHeartRate) result.Add((HealthMetricType.HeartRate, "心率"));
        if (pref.HealthRecordReminderGlucose) result.Add((HealthMetricType.Glucose, "血糖"));
        if (pref.HealthRecordReminderSpO2) result.Add((HealthMetricType.SpO2, "血氧"));
        if (pref.HealthRecordReminderWeight) result.Add((HealthMetricType.Weight, "体重"));
        return result;
    }

    private static bool IsInDnd(NotificationPreference pref, DateTime beijingNow)
    {
        if (!pref.DndEnabled) return false;
        var minuteOfDay = beijingNow.Hour * 60 + beijingNow.Minute;
        return pref.DndStartMinutes <= pref.DndEndMinutes
            ? minuteOfDay >= pref.DndStartMinutes && minuteOfDay < pref.DndEndMinutes
            : minuteOfDay >= pref.DndStartMinutes || minuteOfDay < pref.DndEndMinutes;
    }

    private static bool IsMedicationActiveOn(Medication medication, DateOnly date) =>
        (medication.StartDate == null || medication.StartDate <= date) &&
        (medication.EndDate == null || medication.EndDate >= date);

    private static bool ShouldDoseToday(Medication medication, DateOnly today)
    {
        if (medication.Frequency is MedicationFrequency.Daily
            or MedicationFrequency.TwiceDaily
            or MedicationFrequency.ThreeTimesDaily
            or MedicationFrequency.AsNeeded)
            return true;

        var startDate = medication.StartDate ?? today;
        if (medication.Frequency == MedicationFrequency.EveryOtherDay)
            return (today.DayNumber - startDate.DayNumber) % 2 == 0;
        if (medication.Frequency == MedicationFrequency.Weekly)
            return today.DayOfWeek == startDate.DayOfWeek;
        return true;
    }

    private static bool IsInTimeWindow(TimeOnly value, TimeOnly start, TimeOnly end) =>
        end >= start ? value >= start && value <= end : value >= start || value <= end;

    private static Guid StableGuid(string key)
    {
        var bytes = System.Security.Cryptography.MD5.HashData(System.Text.Encoding.UTF8.GetBytes(key));
        return new Guid(bytes);
    }
}
