using VoidCat.Model;
using VoidCat.Services.Abstractions;
using VoidCat.Services.VirusScanner.Exceptions;

namespace VoidCat.Services.Background;

public class VirusScannerService : BackgroundService
{
    private readonly ILogger<VirusScannerService> _logger;
    private readonly IVirusScanner _scanner;
    private readonly IFileMetadataStore _fileStore;
    private readonly IVirusScanStore _scanStore;

    public VirusScannerService(ILogger<VirusScannerService> logger, IVirusScanner scanner, IVirusScanStore scanStore,
        IFileMetadataStore fileStore)
    {
        _scanner = scanner;
        _logger = logger;
        _scanStore = scanStore;
        _fileStore = fileStore;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Virus scanner background service starting..");

        while (!stoppingToken.IsCancellationRequested)
        {
            var page = 0;
            while (true)
            {
                var files = await _fileStore.ListFiles(new(page++, 1_000));
                if (files.Results == 0) break;

                await foreach (var file in files.Data.WithCancellation(stoppingToken))
                {
                    // file is too large, cant scan
                    if (file.Size > 4_000_000) continue;

                    // check for scans
                    var scan = await _scanStore.GetByFile(file.Id);
                    if (scan == default || scan.ScanTime < DateTime.UtcNow.Subtract(TimeSpan.FromDays(30)))
                    {
                        try
                        {
                            var result = await _scanner.ScanFile(file.Id, stoppingToken);
                            await _scanStore.Add(result.Id, result);
                            _logger.LogInformation("Scanned file {Id}, IsVirus = {Result}", result.File, result.Score);
                        }
                        catch (RateLimitedException rx)
                        {
                            var sleep = rx.RetryAfter ?? DateTimeOffset.UtcNow.AddMinutes(10);
                            _logger.LogWarning("VirusScanner was rate limited, sleeping until {Time}", sleep);
                            await Task.Delay(sleep - DateTimeOffset.UtcNow, stoppingToken);
                        }
                        catch (Exception ex)
                        {
                            _logger.LogError(ex, "Failed to scan file {Id} error={Message}", file.Id, ex.Message);
                        }
                    }
                }
            }

            await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
        }
    }
}
