using Health.Domain;
using Health.Domain.Entities;
using Health.Domain.Enums;

namespace Health.Application.Medications;

public sealed class MedicationService(IMedicationRepository medications) : IMedicationService
{
    private readonly IMedicationRepository _medications = medications;

    public async Task<IReadOnlyList<MedicationDto>> ListAsync(Guid userId, string? filter, CancellationToken ct)
    {
        var (todayStartUtc, todayEndUtc) = GetBeijingTodayUtcWindow();
        var meds = await _medications.ListAsync(userId, filter, ct);
        return meds.Select(m => ToDto(m, HasTakenToday(m, todayStartUtc, todayEndUtc))).ToList();
    }

    public async Task<Guid> CreateAsync(Guid userId, MedicationUpsertRequest request, CancellationToken ct)
    {
        var name = ValidateName(request.Name);
        ValidateDateRange(request.StartDate, request.EndDate);
        var times = NormalizeTimes(request.TimeOfDay);
        if (times.Count == 0) times = [new TimeOnly(8, 0)];

        var med = new Medication
        {
            Id = Guid.NewGuid(),
            UserId = userId,
            Name = name,
            Dosage = request.Dosage,
            Frequency = request.Frequency,
            TimeOfDay = times,
            StartDate = request.StartDate,
            EndDate = request.EndDate,
            Source = request.Source,
            Notes = request.Notes,
            IsActive = true,
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow,
        };

        await _medications.AddMedicationAsync(med, ct);
        await _medications.SaveChangesAsync(ct);
        return med.Id;
    }

    public async Task<bool> UpdateAsync(Guid userId, Guid medicationId, MedicationPatchRequest request, CancellationToken ct)
    {
        var med = await _medications.GetOwnedAsync(userId, medicationId, ct);
        if (med == null) return false;

        if (request.Name != null) med.Name = ValidateName(request.Name);
        if (request.Dosage != null) med.Dosage = request.Dosage;
        if (request.Frequency.HasValue) med.Frequency = request.Frequency.Value;
        if (request.TimeOfDay != null) med.TimeOfDay = NormalizeTimes(request.TimeOfDay);
        if (request.StartDate.HasValue) med.StartDate = request.StartDate.Value;
        if (request.EndDate.HasValue) med.EndDate = request.EndDate.Value;
        ValidateDateRange(med.StartDate, med.EndDate);
        if (request.Notes != null) med.Notes = request.Notes;
        med.UpdatedAt = DateTime.UtcNow;

        await _medications.SaveChangesAsync(ct);
        return true;
    }

    // ── 输入校验 ──

    private static string ValidateName(string? name)
    {
        var trimmed = name?.Trim();
        if (string.IsNullOrEmpty(trimmed))
            throw new ValidationException("药品名称不能为空");
        if (trimmed.Length > 200)
            throw new ValidationException("药品名称过长（不超过 200 字）");
        return trimmed;
    }

    private static void ValidateDateRange(DateOnly? startDate, DateOnly? endDate)
    {
        if (startDate.HasValue && endDate.HasValue && endDate.Value < startDate.Value)
            throw new ValidationException("结束日期不能早于开始日期");
    }

    // 同一时间点去重并排序；空集合交由调用方/实体默认处理
    private static List<TimeOnly> NormalizeTimes(IReadOnlyList<TimeOnly> times) =>
        times.Distinct().OrderBy(t => t).ToList();

    public async Task<bool> DeleteAsync(Guid userId, Guid medicationId, CancellationToken ct)
    {
        var med = await _medications.GetOwnedAsync(userId, medicationId, ct);
        if (med == null) return false;

        _medications.DeleteMedication(med);
        await _medications.SaveChangesAsync(ct);
        return true;
    }

    public async Task<bool?> ToggleTodayTakenAsync(Guid userId, Guid medicationId, CancellationToken ct)
    {
        if (!await _medications.ExistsOwnedAsync(userId, medicationId, ct)) return null;

        var (todayStartUtc, todayEndUtc) = GetBeijingTodayUtcWindow();
        var existing = await _medications.GetTodayTakenLogAsync(userId, medicationId, todayStartUtc, todayEndUtc, ct);
        if (existing != null)
        {
            _medications.DeleteLog(existing);
            await _medications.SaveChangesAsync(ct);
            return false;
        }

        await AddLogAsync(userId, medicationId, MedicationLogStatus.Taken, TimeOnly.FromDateTime(DateTime.UtcNow.AddHours(8)), ct);
        return true;
    }

    public async Task<IReadOnlyList<MedicationReminderDto>> GetRemindersAsync(Guid userId, CancellationToken ct)
    {
        var beijingNow = DateTime.UtcNow.AddHours(8);
        var now = TimeOnly.FromDateTime(beijingNow);
        var today = DateOnly.FromDateTime(beijingNow);
        var (todayStartUtc, todayEndUtc) = GetBeijingTodayUtcWindow();

        var meds = await _medications.ListActiveForRemindersAsync(userId, today, ct);
        var logs = await _medications.ListLogsInWindowAsync(userId, todayStartUtc, todayEndUtc, ct);

        var reminders = new List<MedicationReminderDto>();
        foreach (var med in meds)
        {
            if (!GetDosesForToday(med, today)) continue;

            foreach (var scheduledTime in med.TimeOfDay)
            {
                var log = logs.FirstOrDefault(l => l.MedicationId == med.Id && l.ScheduledTime == scheduledTime);
                string status;
                if (log != null)
                    status = log.Status == MedicationLogStatus.Taken ? "taken" : "skipped";
                else if (scheduledTime <= now.AddMinutes(-30))
                    status = "overdue";
                else if (scheduledTime <= now.AddHours(3))
                    status = "upcoming";
                else
                    continue;

                reminders.Add(new MedicationReminderDto(med.Id, med.Name, med.Dosage, scheduledTime.ToString("HH:mm"), status));
            }
        }

        return reminders
            .OrderBy(r => r.Status == "overdue" ? 0 : r.Status == "upcoming" ? 1 : 2)
            .ToList();
    }

    public async Task<bool?> ConfirmDoseAsync(Guid userId, Guid medicationId, TimeOnly scheduledTime, MedicationLogStatus status, CancellationToken ct)
    {
        if (!await _medications.ExistsOwnedAsync(userId, medicationId, ct)) return null;

        var (todayStartUtc, todayEndUtc) = GetBeijingTodayUtcWindow();
        var existing = await _medications.DoseLogExistsAsync(userId, medicationId, scheduledTime, todayStartUtc, todayEndUtc, ct);
        if (existing) return false;

        await AddLogAsync(userId, medicationId, status, scheduledTime, ct);
        return true;
    }

    public async Task<bool> CancelDoseAsync(Guid userId, Guid medicationId, TimeOnly scheduledTime, CancellationToken ct)
    {
        var (todayStartUtc, todayEndUtc) = GetBeijingTodayUtcWindow();
        var log = await _medications.GetDoseLogAsync(userId, medicationId, scheduledTime, todayStartUtc, todayEndUtc, ct);
        if (log == null) return false;

        _medications.DeleteLog(log);
        await _medications.SaveChangesAsync(ct);
        return true;
    }

    public Task<IReadOnlyList<MedicationDto>> ListActiveAsync(Guid userId, CancellationToken ct) =>
        ListAsync(userId, "active", ct);

    public async Task<bool> ConfirmNowAsync(Guid userId, Guid medicationId, CancellationToken ct)
    {
        var result = await ConfirmDoseAsync(
            userId,
            medicationId,
            TimeOnly.FromDateTime(DateTime.UtcNow.AddHours(8)),
            MedicationLogStatus.Taken,
            ct);
        return result == true;
    }

    private async Task AddLogAsync(Guid userId, Guid medicationId, MedicationLogStatus status, TimeOnly scheduledTime, CancellationToken ct)
    {
        await _medications.AddLogAsync(new MedicationLog
        {
            Id = Guid.NewGuid(),
            MedicationId = medicationId,
            UserId = userId,
            Status = status,
            ScheduledTime = scheduledTime,
            ConfirmedAt = DateTime.UtcNow,
            CreatedAt = DateTime.UtcNow,
        }, ct);
        await _medications.SaveChangesAsync(ct);
    }

    private static MedicationDto ToDto(Medication med, bool todayTaken) => new(
        med.Id,
        med.Name,
        med.Dosage,
        med.Frequency.ToString(),
        med.TimeOfDay,
        med.StartDate,
        med.EndDate,
        med.IsActive,
        med.Notes,
        med.Source.ToString(),
        med.CreatedAt,
        med.UpdatedAt,
        todayTaken);

    private static bool HasTakenToday(Medication med, DateTime todayStartUtc, DateTime todayEndUtc) =>
        med.Logs.Any(l => l.CreatedAt >= todayStartUtc && l.CreatedAt < todayEndUtc && l.Status == MedicationLogStatus.Taken);

    private static (DateTime StartUtc, DateTime EndUtc) GetBeijingTodayUtcWindow()
    {
        var beijingToday = DateTime.UtcNow.AddHours(8).Date;
        var todayStartUtc = beijingToday.AddHours(-8);
        return (todayStartUtc, todayStartUtc.AddDays(1));
    }

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

        var startDate = med.StartDate ?? today;
        if (med.Frequency == MedicationFrequency.EveryOtherDay)
            return (today.DayNumber - startDate.DayNumber) % 2 == 0;

        if (med.Frequency == MedicationFrequency.Weekly)
            return today.DayOfWeek == startDate.DayOfWeek;

        return true;
    }
}
