using JobTrackerApi.Services;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;

namespace JobTrackerApi.Tests;

public sealed class DatabaseBackupRunnerTests : IDisposable
{
    private readonly string _root;

    public DatabaseBackupRunnerTests()
    {
        _root = Path.Combine(Path.GetTempPath(), $"jt-backup-tests-{Guid.NewGuid():N}");
        Directory.CreateDirectory(_root);
    }

    public void Dispose()
    {
        SqliteConnection.ClearAllPools();
        try { Directory.Delete(_root, recursive: true); } catch (IOException) { }
    }

    private string CreateSourceDb(out string connectionString)
    {
        var dbPath = Path.Combine(_root, "source.db");
        connectionString = $"Data Source={dbPath}";
        using var connection = new SqliteConnection(connectionString);
        connection.Open();
        using var command = connection.CreateCommand();
        command.CommandText = "CREATE TABLE Sample (Id INTEGER PRIMARY KEY, Name TEXT); INSERT INTO Sample (Name) VALUES ('alpha'), ('beta');";
        command.ExecuteNonQuery();
        return dbPath;
    }

    private SqliteDatabaseBackupRunner CreateRunner(string connectionString, int retainCount = 14)
        => new(connectionString, Path.Combine(_root, "backups"), retainCount, NullLogger<SqliteDatabaseBackupRunner>.Instance);

    [Fact]
    public async Task RunOnce_creates_a_restorable_backup_file()
    {
        CreateSourceDb(out var connectionString);
        var runner = CreateRunner(connectionString);

        var backupPath = await runner.RunOnceAsync(CancellationToken.None);

        Assert.NotNull(backupPath);
        Assert.True(File.Exists(backupPath));

        await using var verify = new SqliteConnection($"Data Source={backupPath}");
        await verify.OpenAsync();
        await using var count = verify.CreateCommand();
        count.CommandText = "SELECT COUNT(*) FROM Sample";
        Assert.Equal(2L, (long)(await count.ExecuteScalarAsync())!);
    }

    [Fact]
    public async Task RunOnce_prunes_backups_beyond_retention()
    {
        CreateSourceDb(out var connectionString);
        var runner = CreateRunner(connectionString, retainCount: 2);
        var backupsRoot = runner.BackupsRoot;
        Directory.CreateDirectory(backupsRoot);

        for (var i = 0; i < 3; i++)
        {
            var stale = Path.Combine(backupsRoot, $"{SqliteDatabaseBackupRunner.BackupFilePrefix}stale{i}.db");
            File.WriteAllText(stale, "stale");
            File.SetLastWriteTimeUtc(stale, DateTime.UtcNow.AddDays(-10 - i));
        }

        await runner.RunOnceAsync(CancellationToken.None);

        var remaining = Directory.GetFiles(backupsRoot, $"{SqliteDatabaseBackupRunner.BackupFilePrefix}*.db");
        Assert.Equal(2, remaining.Length);
        Assert.Contains(remaining, f => Path.GetFileName(f).Contains("stale0"));
    }

    [Fact]
    public void Latest_backup_timestamp_reflects_newest_file()
    {
        CreateSourceDb(out var connectionString);
        var runner = CreateRunner(connectionString);

        Assert.Null(runner.GetLatestBackupUtc());

        Directory.CreateDirectory(runner.BackupsRoot);
        var file = Path.Combine(runner.BackupsRoot, $"{SqliteDatabaseBackupRunner.BackupFilePrefix}x.db");
        File.WriteAllText(file, "x");
        var stamp = DateTime.UtcNow.AddHours(-3);
        File.SetLastWriteTimeUtc(file, stamp);

        var latest = runner.GetLatestBackupUtc();
        Assert.NotNull(latest);
        Assert.True(Math.Abs((latest!.Value - stamp).TotalSeconds) < 2);
    }
}
