using Ikar.Server.Data;
using Ikar.Server.Infrastructure.Hubs;
using Ikar.Server.Infrastructure.Push;
using Ikar.Shared;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;

namespace Ikar.Server.Infrastructure.Updates;

public sealed class AndroidAppUpdateMonitorService(
    IServiceScopeFactory serviceScopeFactory,
    AndroidAppUpdateService androidAppUpdateService,
    IHubContext<MessengerHub> hubContext,
    ILogger<AndroidAppUpdateMonitorService> logger) : BackgroundService
{
    private static readonly TimeSpan PollInterval = TimeSpan.FromSeconds(15);
    private AndroidAppReleaseDto? _lastObservedRelease;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _lastObservedRelease = await androidAppUpdateService.GetLatestReleaseAsync(stoppingToken);

        using var timer = new PeriodicTimer(PollInterval);
        while (await timer.WaitForNextTickAsync(stoppingToken))
        {
            try
            {
                var currentRelease = await androidAppUpdateService.GetLatestReleaseAsync(stoppingToken);
                if (currentRelease is null)
                {
                    continue;
                }

                if (!HasRelevantUpdateChange(_lastObservedRelease, currentRelease))
                {
                    continue;
                }

                var previousRelease = _lastObservedRelease;
                _lastObservedRelease = currentRelease;
                await NotifyClientsAsync(currentRelease, stoppingToken);
                await NotifyPushDevicesAsync(currentRelease, stoppingToken);

                logger.LogInformation(
                    "Detected Android app update change from version {PreviousVersion} to {CurrentVersion}.",
                    previousRelease?.VersionName ?? "none",
                    currentRelease.VersionName);
            }
            catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
            {
                break;
            }
            catch (Exception ex)
            {
            logger.LogWarning(ex, "Android app update monitor failed during polling.");
            }
        }
    }

    private static bool HasRelevantUpdateChange(AndroidAppReleaseDto? previousRelease, AndroidAppReleaseDto currentRelease)
    {
        if (previousRelease is null)
        {
            return true;
        }

        return previousRelease.VersionCode != currentRelease.VersionCode ||
               !string.Equals(previousRelease.VersionName, currentRelease.VersionName, StringComparison.Ordinal) ||
               !string.Equals(previousRelease.DownloadPath, currentRelease.DownloadPath, StringComparison.Ordinal);
    }

    private Task NotifyClientsAsync(AndroidAppReleaseDto release, CancellationToken cancellationToken) =>
        hubContext.Clients.All.SendAsync("AppUpdateAvailable", release, cancellationToken);

    private async Task NotifyPushDevicesAsync(AndroidAppReleaseDto release, CancellationToken cancellationToken)
    {
        await using var scope = serviceScopeFactory.CreateAsyncScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<IkarDbContext>();
        var pushNotificationSender = scope.ServiceProvider.GetRequiredService<IPushNotificationSender>();

        var devices = await dbContext.PushDevices
            .Where(device => device.Platform == PushPlatform.Android && device.NotificationsEnabled)
            .ToListAsync(cancellationToken);

        if (devices.Count == 0)
        {
            return;
        }

        try
        {
            await pushNotificationSender.SendAppUpdateAvailableAsync(release, devices, cancellationToken);
        }
        catch (Exception ex)
        {
            logger.LogWarning(ex, "Android app update push dispatch failed for version {VersionCode}.", release.VersionCode);
        }
    }
}
