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

namespace Health.Application.Exercises;

public sealed class ExerciseService(IExerciseRepository exercises) : IExerciseService
{
    private const int MaxDurationMinutes = 1440;  // 单次运动时长上限 24 小时
    private static readonly TimeOnly DefaultReminderTime = new(19, 0);
    private readonly IExerciseRepository _exercises = exercises;

    public async Task<ExercisePlanDto?> GetCurrentAsync(Guid userId, CancellationToken ct)
    {
        var today = BeijingToday();
        var plans = await _exercises.ListActiveOnAsync(userId, today, ct);
        var plan = plans.FirstOrDefault(p => p.Items.Any(i => i.ScheduledDate == today));
        return plan == null ? null : ToDto(plan);
    }

    public async Task<Guid> CreateAsync(Guid userId, ExercisePlanCreateRequest request, CancellationToken ct)
    {
        ValidateDuration(request.DurationMinutes);

        var startDate = request.StartDate;
        var endDate = request.EndDate < startDate ? startDate : request.EndDate;
        if (endDate.DayNumber - startDate.DayNumber > 365)
            endDate = startDate.AddDays(365);

        var exerciseType = string.IsNullOrWhiteSpace(request.ExerciseType) ? "运动" : request.ExerciseType.Trim();
        var duration = request.DurationMinutes > 0 ? request.DurationMinutes : 30;
        var plan = NewPlan(userId, startDate, endDate, request.ReminderTime);
        for (var date = startDate; date <= endDate; date = date.AddDays(1))
        {
            plan.Items.Add(new ExercisePlanItem
            {
                Id = Guid.NewGuid(), ScheduledDate = date, ExerciseType = exerciseType,
                DurationMinutes = duration, IsRestDay = false,
            });
        }
        await SaveNewAsync(plan, ct);
        return plan.Id;
    }


    // 时长上限校验：下限交由各方法兜底为 30，这里只拦截不合理的超大值
    private static void ValidateDuration(int durationMinutes)
    {
        if (durationMinutes > MaxDurationMinutes)
            throw new ValidationException($"单次运动时长不能超过 {MaxDurationMinutes} 分钟（24 小时），当前值 {durationMinutes} 不合理");
    }

    public async Task<IReadOnlyList<ExercisePlanSummaryDto>> ListAsync(Guid userId, CancellationToken ct)
    {
        var plans = await _exercises.ListAsync(userId, 20, ct);
        return plans.Select(p => new ExercisePlanSummaryDto(
            p.Id, p.StartDate, p.EndDate, p.ReminderTime, p.CreatedAt,
            p.Items.Count, p.Items.Count(i => i.IsCompleted),
            p.Items.OrderBy(i => i.ScheduledDate).Select(ToItemDto).ToList())).ToList();
    }

    public async Task<ExercisePlanDto?> GetByIdAsync(Guid userId, Guid planId, CancellationToken ct)
    {
        var plan = await _exercises.GetOwnedPlanAsync(userId, planId, ct);
        return plan == null ? null : ToDto(plan);
    }

    public async Task<bool> DeleteAsync(Guid userId, Guid planId, CancellationToken ct)
    {
        var plan = await _exercises.GetOwnedPlanAsync(userId, planId, ct);
        if (plan == null) return false;
        _exercises.Delete(plan);
        await _exercises.SaveChangesAsync(ct);
        return true;
    }

    public async Task<bool?> ToggleCheckInAsync(Guid userId, Guid itemId, CancellationToken ct)
    {
        var item = await _exercises.GetOwnedItemAsync(userId, itemId, ct);
        if (item == null) return null;
        if (item.ScheduledDate != BeijingToday())
            throw new ValidationException("只能打卡今天的运动任务");
        if (item.IsRestDay)
            throw new ValidationException("休息日无需打卡");
        item.IsCompleted = !item.IsCompleted;
        item.CompletedAt = item.IsCompleted ? DateTime.UtcNow : null;
        item.Plan.UpdatedAt = DateTime.UtcNow;
        await _exercises.SaveChangesAsync(ct);
        return item.IsCompleted;
    }

    public async Task<bool> CheckInAsync(Guid userId, Guid itemId, CancellationToken ct)
    {
        var item = await _exercises.GetOwnedItemAsync(userId, itemId, ct);
        if (item == null) return false;
        if (item.ScheduledDate != BeijingToday())
            throw new ValidationException("只能打卡今天的运动任务");
        if (item.IsRestDay)
            throw new ValidationException("休息日无需打卡");
        item.IsCompleted = true;
        item.CompletedAt = DateTime.UtcNow;
        item.Plan.UpdatedAt = DateTime.UtcNow;
        await _exercises.SaveChangesAsync(ct);
        return true;
    }

    public async Task<ExercisePlanDto?> GetLatestAsync(Guid userId, CancellationToken ct)
    {
        var plan = await _exercises.GetLatestAsync(userId, ct);
        return plan == null ? null : ToDto(plan);
    }

    private async Task SaveNewAsync(ExercisePlan plan, CancellationToken ct)
    {
        await _exercises.AddAsync(plan, ct);
        await _exercises.SaveChangesAsync(ct);
    }

    private static ExercisePlan NewPlan(Guid userId, DateOnly startDate, DateOnly endDate, TimeOnly? reminderTime) => new()
    {
        Id = Guid.NewGuid(), UserId = userId, StartDate = startDate, EndDate = endDate,
        ReminderTime = reminderTime ?? DefaultReminderTime,
        CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow,
    };

    private static ExercisePlanDto ToDto(ExercisePlan plan) => new(
        plan.Id, plan.StartDate, plan.EndDate, plan.ReminderTime, plan.CreatedAt, plan.UpdatedAt,
        plan.Items.OrderBy(i => i.ScheduledDate).Select(ToItemDto).ToList());

    private static ExercisePlanItemDto ToItemDto(ExercisePlanItem item) => new(
        item.Id, item.ScheduledDate, item.ExerciseType, item.DurationMinutes,
        item.IsCompleted, item.CompletedAt, item.IsRestDay);

    private static DateOnly BeijingToday() => DateOnly.FromDateTime(DateTime.UtcNow.AddHours(8));
}
