using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace ScadaLink.SiteEventLogging;

/// <summary>
/// Background service that periodically purges old events from the SQLite event log.
/// Enforces both time-based retention (default 30 days) and storage cap (default 1GB).
/// Runs on a background thread and does not block event recording.
/// </summary>
public class EventLogPurgeService : BackgroundService
{
    private readonly SiteEventLogger _eventLogger;
    private readonly SiteEventLogOptions _options;
    private readonly ILogger<EventLogPurgeService> _logger;

    public EventLogPurgeService(
        ISiteEventLogger eventLogger,
        IOptions<SiteEventLogOptions> options,
        ILogger<EventLogPurgeService> logger)
    {
        // We need the concrete type to access the connection
        _eventLogger = (SiteEventLogger)eventLogger;
        _options = options.Value;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Event log purge service started — retention: {Days} days, cap: {Cap} MB, interval: {Interval}",
            _options.RetentionDays, _options.MaxStorageMb, _options.PurgeInterval);

        using var timer = new PeriodicTimer(_options.PurgeInterval);

        // Run an initial purge on startup
        RunPurge();

        while (await timer.WaitForNextTickAsync(stoppingToken).ConfigureAwait(false))
        {
            RunPurge();
        }
    }

    internal void RunPurge()
    {
        try
        {
            PurgeByRetention();
            PurgeByStorageCap();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error during event log purge");
        }
    }

    private void PurgeByRetention()
    {
        var cutoff = DateTimeOffset.UtcNow.AddDays(-_options.RetentionDays).ToString("o");

        using var cmd = _eventLogger.Connection.CreateCommand();
        cmd.CommandText = "DELETE FROM site_events WHERE timestamp < $cutoff";
        cmd.Parameters.AddWithValue("$cutoff", cutoff);
        var deleted = cmd.ExecuteNonQuery();

        if (deleted > 0)
        {
            _logger.LogInformation("Purged {Count} events older than {Days} days", deleted, _options.RetentionDays);
        }
    }

    private void PurgeByStorageCap()
    {
        var currentSizeBytes = GetDatabaseSizeBytes();
        var capBytes = (long)_options.MaxStorageMb * 1024 * 1024;

        if (currentSizeBytes <= capBytes)
            return;

        _logger.LogWarning(
            "Event log size {Size:F1} MB exceeds cap {Cap} MB — purging oldest events",
            currentSizeBytes / (1024.0 * 1024.0), _options.MaxStorageMb);

        // Delete oldest events in batches until under the cap
        while (currentSizeBytes > capBytes)
        {
            using var cmd = _eventLogger.Connection.CreateCommand();
            cmd.CommandText = """
                DELETE FROM site_events WHERE id IN (
                    SELECT id FROM site_events ORDER BY id ASC LIMIT 1000
                )
                """;
            var deleted = cmd.ExecuteNonQuery();
            if (deleted == 0) break;

            // Reclaim space
            using var vacuumCmd = _eventLogger.Connection.CreateCommand();
            vacuumCmd.CommandText = "PRAGMA incremental_vacuum";
            vacuumCmd.ExecuteNonQuery();

            currentSizeBytes = GetDatabaseSizeBytes();
        }
    }

    internal long GetDatabaseSizeBytes()
    {
        using var pageCountCmd = _eventLogger.Connection.CreateCommand();
        pageCountCmd.CommandText = "PRAGMA page_count";
        var pageCount = (long)pageCountCmd.ExecuteScalar()!;

        using var pageSizeCmd = _eventLogger.Connection.CreateCommand();
        pageSizeCmd.CommandText = "PRAGMA page_size";
        var pageSize = (long)pageSizeCmd.ExecuteScalar()!;

        return pageCount * pageSize;
    }
}
