using System.IO;
using FluentAssertions;
using DragonISO.App.Services;

namespace DragonISO.App.Tests.Services;

// UpdateChecker unit tests.
//
// We don't exercise CheckAsync (the real HTTP call against Forgejo) â€”
// tests must not depend on the network. Coverage instead:
//   â€¢ TryParseSemVer: version-comparison parsing across the inputs the
//     real release stream produces.
//   â€¢ CheckIfDueAsync throttle: a recent cooldown stamp short-circuits
//     and returns null *before* CheckAsync runs (which would otherwise
//     fire an HTTP request).
public sealed class UpdateCheckerTests : IDisposable
{
    private readonly string _tempDir;
    private readonly string? _previousOverride;

    public UpdateCheckerTests()
    {
        _tempDir = Path.Combine(Path.GetTempPath(), $"Dragon-ISO-update-{Guid.NewGuid():N}");
        Directory.CreateDirectory(_tempDir);
        _previousOverride = UpdateChecker.StateDirectoryOverride;
        UpdateChecker.StateDirectoryOverride = _tempDir;
    }

    public void Dispose()
    {
        UpdateChecker.StateDirectoryOverride = _previousOverride;
        try { if (Directory.Exists(_tempDir)) Directory.Delete(_tempDir, recursive: true); }
        catch { /* cleanup best-effort */ }
    }

    [Theory]
    [InlineData("v1.2.3", "1.2.3")]
    [InlineData("V1.2.3", "1.2.3")]      // case-insensitive 'v' strip
    [InlineData("1.2.3",  "1.2.3")]
    [InlineData("v1.2.3.4", "1.2.3.4")]  // 4-segment .NET-style versions
    [InlineData("v1.2.3-alpha", "1.2.3")] // pre-release suffix stripped
    [InlineData("v1.2.3-beta.4", "1.2.3")]
    public void TryParseSemVer_AcceptsExpectedForms(string input, string expected)
    {
        UpdateChecker.TryParseSemVer(input).Should().Be(Version.Parse(expected));
    }

    [Theory]
    [InlineData("not-a-version")]
    [InlineData("v.invalid")]
    [InlineData("")]
    public void TryParseSemVer_ReturnsNullOnGarbage(string input)
    {
        UpdateChecker.TryParseSemVer(input).Should().BeNull();
    }

    [Fact]
    public void TryParseSemVer_OrderingIsSemantic()
    {
        // The CheckAsync comparison is "latest > current" â€” pin the
        // ordering across the version arc the release process actually
        // produces.
        var older  = UpdateChecker.TryParseSemVer("v0.1.0")!;
        var newer  = UpdateChecker.TryParseSemVer("v0.2.0")!;
        var newest = UpdateChecker.TryParseSemVer("v1.0.0")!;

        (newer  > older).Should().BeTrue();
        (newest > newer).Should().BeTrue();
        (newest > older).Should().BeTrue();
        (older  > newer).Should().BeFalse();
    }

    [Fact]
    public async Task CheckIfDueAsync_ReturnsNull_WhenCooldownStampIsRecent()
    {
        // Pre-write a "we just checked" stamp. The throttle should
        // short-circuit and return null without firing the HTTP call,
        // which means the test passes deterministically offline.
        File.WriteAllText(
            Path.Combine(_tempDir, "last-update-check.txt"),
            DateTimeOffset.UtcNow.ToString("o"));

        var result = await UpdateChecker.CheckIfDueAsync(TimeSpan.FromHours(24));

        result.Should().BeNull("a stamp inside the cooldown window suppresses the check");
    }

    [Fact]
    public async Task CheckIfDueAsync_ReturnsNull_WhenStampIsOldButCooldownIsLargerThanGap()
    {
        // Edge case: stamp 1h old, cooldown 24h â†’ still suppressed.
        File.WriteAllText(
            Path.Combine(_tempDir, "last-update-check.txt"),
            DateTimeOffset.UtcNow.AddHours(-1).ToString("o"));

        var result = await UpdateChecker.CheckIfDueAsync(TimeSpan.FromHours(24));

        result.Should().BeNull();
    }

    [Fact]
    public void LaunchCheckEnabled_RoundTrips()
    {
        // Default (no flag file) â†’ enabled.
        UpdateChecker.LaunchCheckEnabled.Should().BeTrue();

        UpdateChecker.LaunchCheckEnabled = false;
        UpdateChecker.LaunchCheckEnabled.Should().BeFalse(
            "writing the opt-out flag should be visible immediately");
        File.Exists(Path.Combine(_tempDir, "no-update-check.flag"))
            .Should().BeTrue();

        UpdateChecker.LaunchCheckEnabled = true;
        UpdateChecker.LaunchCheckEnabled.Should().BeTrue();
        File.Exists(Path.Combine(_tempDir, "no-update-check.flag"))
            .Should().BeFalse("re-enabling should remove the opt-out flag");
    }
}
