using Microsoft.EntityFrameworkCore;
using Quartz;
using OTSSignsOrchestrator.Server.Clients;
using OTSSignsOrchestrator.Server.Data;
using OTSSignsOrchestrator.Server.Data.Entities;

namespace OTSSignsOrchestrator.Server.Jobs;

/// <summary>
/// Quartz job scheduled at 2 AM UTC daily (<c>0 0 2 * * ?</c>).
/// For each active <see cref="Instance"/>: calls <see cref="XiboApiClientExtensions.GetAllPagesAsync{T}"/>
/// with <c>authorised=1</c> to count authorised displays; inserts a <see cref="ScreenSnapshot"/> row.
///
/// Uses ON CONFLICT DO NOTHING semantics to protect against double-runs.
/// THIS DATA CANNOT BE RECOVERED — if the job misses a day, that data is permanently lost.
/// </summary>
[DisallowConcurrentExecution]
public sealed class DailySnapshotJob : IJob
{
    private readonly IServiceProvider _services;
    private readonly ILogger<DailySnapshotJob> _logger;

    public DailySnapshotJob(
        IServiceProvider services,
        ILogger<DailySnapshotJob> logger)
    {
        _services = services;
        _logger = logger;
    }

    public async Task Execute(IJobExecutionContext context)
    {
        await using var scope = _services.CreateAsyncScope();
        var db = scope.ServiceProvider.GetRequiredService<OrchestratorDbContext>();
        var clientFactory = scope.ServiceProvider.GetRequiredService<XiboClientFactory>();
        var settings = scope.ServiceProvider.GetRequiredService<Core.Services.SettingsService>();

        var today = DateOnly.FromDateTime(DateTime.UtcNow);

        var instances = await db.Instances
            .Include(i => i.Customer)
            .Include(i => i.OauthAppRegistries)
            .Where(i => i.Customer.Status == CustomerStatus.Active)
            .ToListAsync(context.CancellationToken);

        _logger.LogInformation("DailySnapshotJob: processing {Count} active instance(s) for {Date}",
            instances.Count, today);

        foreach (var instance in instances)
        {
            var abbrev = instance.Customer.Abbreviation;
            try
            {
                var oauthApp = instance.OauthAppRegistries.FirstOrDefault();
                if (oauthApp is null)
                {
                    _logger.LogWarning(
                        "DailySnapshotJob: skipping {Abbrev} — no OAuth app registered", abbrev);
                    continue;
                }

                var secret = await settings.GetAsync(
                    Core.Services.SettingsService.InstanceOAuthSecretId(abbrev));
                if (string.IsNullOrEmpty(secret))
                {
                    _logger.LogWarning(
                        "DailySnapshotJob: skipping {Abbrev} — OAuth secret not found", abbrev);
                    continue;
                }

                var client = await clientFactory.CreateAsync(
                    instance.XiboUrl, oauthApp.ClientId, secret);

                var displays = await client.GetAllPagesAsync(
                    (start, length) => client.GetDisplaysAsync(start, length, authorised: 1));

                var screenCount = displays.Count;

                // ON CONFLICT DO NOTHING — protect against double-runs.
                // Check if a snapshot already exists for this instance + date.
                var exists = await db.ScreenSnapshots.AnyAsync(
                    s => s.InstanceId == instance.Id && s.SnapshotDate == today,
                    context.CancellationToken);

                if (!exists)
                {
                    db.ScreenSnapshots.Add(new ScreenSnapshot
                    {
                        Id = Guid.NewGuid(),
                        InstanceId = instance.Id,
                        SnapshotDate = today,
                        ScreenCount = screenCount,
                        CreatedAt = DateTime.UtcNow,
                    });

                    _logger.LogInformation(
                        "DailySnapshotJob: {Abbrev} — {Count} authorised display(s)",
                        abbrev, screenCount);
                }
                else
                {
                    _logger.LogInformation(
                        "DailySnapshotJob: {Abbrev} — snapshot already exists for {Date}, skipping",
                        abbrev, today);
                }
            }
            catch (Exception ex)
            {
                // THIS DATA CANNOT BE RECOVERED — log prominently
                _logger.LogWarning(ex,
                    "DailySnapshotJob: FAILED to capture snapshot for {Abbrev} on {Date}. " +
                    "This data is permanently lost.", abbrev, today);
            }
        }

        await db.SaveChangesAsync(context.CancellationToken);
    }
}
