using foodmarket.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;

namespace foodmarket.Api.Background;

/// <summary>Раз в сутки переписывает ReferencePrice = Cost для товаров,
/// у которых LastSupplyAt старше 30 дней и Cost > 0. Цель: устаревшая
/// «эталонная» цена не остаётся годами — её сравнивают с актуальной
/// себестоимостью. Если пользователь редактировал ReferencePrice вручную
/// (через PUT /api/catalog/products/...), ReferencePriceUpdatedAt уходит
/// в now, и таймер начинает 30 дней заново.</summary>
public class ReferencePriceRefreshJob : BackgroundService
{
    private readonly IServiceProvider _services;
    private readonly ILogger<ReferencePriceRefreshJob> _log;
    // Запускаем каждые 24 часа. Промах между сутками не критичен — цена
    // не падает, а лишь догоняет текущую Cost.
    private static readonly TimeSpan Period = TimeSpan.FromHours(24);

    public ReferencePriceRefreshJob(IServiceProvider services, ILogger<ReferencePriceRefreshJob> log)
    {
        _services = services;
        _log = log;
    }

    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        // Ждём 5 минут после старта чтобы не упасть на ещё не применённой миграции.
        try { await Task.Delay(TimeSpan.FromMinutes(5), ct); } catch (TaskCanceledException) { return; }

        using var timer = new PeriodicTimer(Period);
        do
        {
            try { await RunOnceAsync(ct); }
            catch (Exception ex) { _log.LogError(ex, "ReferencePriceRefreshJob: iteration failed"); }
        }
        while (await timer.WaitForNextTickAsync(ct));
    }

    public async Task RunOnceAsync(CancellationToken ct)
    {
        using var scope = _services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var threshold = DateTime.UtcNow.AddDays(-30);
        // IgnoreQueryFilters — фоновый job работает над всеми organizations.
        var stale = await db.Products
            .IgnoreQueryFilters()
            .Where(p => p.LastSupplyAt != null && p.LastSupplyAt < threshold && p.Cost > 0m)
            .ToListAsync(ct);
        if (stale.Count == 0) return;
        var now = DateTime.UtcNow;
        foreach (var p in stale)
        {
            p.ReferencePrice = p.Cost;
            p.ReferencePriceUpdatedAt = now;
        }
        await db.SaveChangesAsync(ct);
        _log.LogInformation("ReferencePriceRefreshJob: refreshed {Count} stale products", stale.Count);
    }
}
