using System.Globalization;
using PowderCoating.Application.Constants;
using PowderCoating.Application.Interfaces;

namespace PowderCoating.Web.BackgroundServices;

/// <summary>
/// Runs the Columbia Coatings catalog sync on a schedule. Wakes hourly and triggers a full sync
/// only when the master switch (<c>ColumbiaSyncEnabled</c>) is on and the configured interval
/// (<c>ColumbiaSyncIntervalDays</c>) has elapsed since the last successful run. A full sync is
/// cheap (~25 API calls), so an hourly due-check is negligible; the actual work runs at most once
/// per interval. No-ops quietly when disabled or unconfigured.
/// </summary>
public class ColumbiaCatalogSyncBackgroundService : BackgroundService
{
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly ILogger<ColumbiaCatalogSyncBackgroundService> _logger;

    private static readonly TimeSpan CheckInterval = TimeSpan.FromHours(1);
    private static readonly TimeSpan StartupDelay = TimeSpan.FromMinutes(2);

    /// <summary>
    /// Uses <see cref="IServiceScopeFactory"/> because a <see cref="BackgroundService"/> is a
    /// singleton and the sync service / platform settings are scoped.
    /// </summary>
    public ColumbiaCatalogSyncBackgroundService(
        IServiceScopeFactory scopeFactory,
        ILogger<ColumbiaCatalogSyncBackgroundService> logger)
    {
        _scopeFactory = scopeFactory;
        _logger = logger;
    }

    /// <inheritdoc />
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("ColumbiaCatalogSyncBackgroundService started.");

        try
        {
            await Task.Delay(StartupDelay, stoppingToken);

            while (!stoppingToken.IsCancellationRequested)
            {
                await RunIfDueAsync(stoppingToken);
                await Task.Delay(CheckInterval, stoppingToken);
            }
        }
        catch (OperationCanceledException)
        {
            // Shutting down — expected.
        }
    }

    /// <summary>
    /// Checks the enable switch and the elapsed interval, and runs a sync when due. Failures from
    /// the sync itself are reported on its result (and recorded in platform settings) rather than
    /// thrown, so a bad run never tears down the loop.
    /// </summary>
    private async Task RunIfDueAsync(CancellationToken ct)
    {
        using var scope = _scopeFactory.CreateScope();
        var settings = scope.ServiceProvider.GetRequiredService<IPlatformSettingsService>();

        if (!await settings.GetBoolAsync(ColumbiaIntegrationConstants.SettingEnabled))
            return; // master switch off

        var intervalDays = Math.Max(1, await settings.GetIntAsync(
            ColumbiaIntegrationConstants.SettingIntervalDays,
            ColumbiaIntegrationConstants.DefaultSyncIntervalDays));

        if (!IsDue(await settings.GetAsync(ColumbiaIntegrationConstants.SettingLastSyncedAt), intervalDays))
            return; // synced recently enough

        var sync = scope.ServiceProvider.GetRequiredService<IColumbiaCatalogSyncService>();

        try
        {
            _logger.LogInformation("Columbia scheduled sync starting (interval {Days}d).", intervalDays);
            var result = await sync.RunSyncAsync(ct);

            if (result.Success)
                _logger.LogInformation("Columbia scheduled sync complete: {Summary}", result.Summary);
            else
                _logger.LogWarning("Columbia scheduled sync did not succeed: {Error}", result.ErrorMessage);
        }
        catch (OperationCanceledException)
        {
            throw;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Columbia scheduled sync threw unexpectedly.");
        }
    }

    /// <summary>
    /// A sync is due when there is no recorded last-sync timestamp, or the configured number of
    /// days has elapsed since it. An unparseable timestamp is treated as "due".
    /// </summary>
    private static bool IsDue(string? lastSyncedRaw, int intervalDays)
    {
        if (string.IsNullOrWhiteSpace(lastSyncedRaw))
            return true;

        if (!DateTime.TryParse(lastSyncedRaw, CultureInfo.InvariantCulture,
                DateTimeStyles.RoundtripKind, out var lastSynced))
            return true;

        return DateTime.UtcNow - lastSynced.ToUniversalTime() >= TimeSpan.FromDays(intervalDays);
    }
}
