using ClaudeDo.Installer.Core;
using ClaudeDo.Releases;

namespace ClaudeDo.Installer.Tests;

public sealed class InstallModeDetectorTests : IDisposable
{
    private readonly string _tempDir;

    public InstallModeDetectorTests()
    {
        _tempDir = Path.Combine(Path.GetTempPath(), "ClaudeDoDetector-" + Guid.NewGuid().ToString("N"));
        Directory.CreateDirectory(_tempDir);
    }

    public void Dispose()
    {
        try { Directory.Delete(_tempDir, recursive: true); } catch { }
    }

    private sealed class FakeReleaseClient : IReleaseClient
    {
        public GiteaRelease? Release { get; set; }
        public Task<GiteaRelease?> GetLatestReleaseAsync(CancellationToken ct) => Task.FromResult(Release);
        public Task DownloadAsync(string url, string destPath, IProgress<long> progress, CancellationToken ct)
            => throw new NotSupportedException();
    }

    [Fact]
    public async Task Detect_FreshInstall_WhenManifestMissing()
    {
        var detector = new InstallModeDetector(new FakeReleaseClient());

        var state = await detector.DetectAsync(_tempDir, CancellationToken.None);

        Assert.Equal(InstallerMode.FreshInstall, state.Mode);
        Assert.Null(state.Existing);
    }

    [Fact]
    public async Task Detect_Config_WhenManifestPresent_And_Api_Unreachable()
    {
        InstallManifestStore.Write(_tempDir,
            new InstallManifest("0.1.0", _tempDir, _tempDir, DateTimeOffset.UtcNow));

        var detector = new InstallModeDetector(new FakeReleaseClient { Release = null });

        var state = await detector.DetectAsync(_tempDir, CancellationToken.None);

        Assert.Equal(InstallerMode.Config, state.Mode);
        Assert.Equal("0.1.0", state.Existing!.Version);
    }

    [Fact]
    public async Task Detect_Update_WhenLatest_GreaterThan_Installed()
    {
        InstallManifestStore.Write(_tempDir,
            new InstallManifest("0.1.0", _tempDir, _tempDir, DateTimeOffset.UtcNow));

        var fake = new FakeReleaseClient
        {
            Release = new GiteaRelease("v0.2.0", "v0.2.0", Array.Empty<ReleaseAsset>())
        };
        var detector = new InstallModeDetector(fake);

        var state = await detector.DetectAsync(_tempDir, CancellationToken.None);

        Assert.Equal(InstallerMode.Update, state.Mode);
        Assert.Equal("0.1.0", state.Existing!.Version);
        Assert.Equal("0.2.0", state.LatestVersion);
    }

    [Fact]
    public async Task Detect_Config_WhenLatest_EqualsOrOlderThan_Installed()
    {
        InstallManifestStore.Write(_tempDir,
            new InstallManifest("0.2.0", _tempDir, _tempDir, DateTimeOffset.UtcNow));

        var fake = new FakeReleaseClient
        {
            Release = new GiteaRelease("v0.2.0", "v0.2.0", Array.Empty<ReleaseAsset>())
        };
        var detector = new InstallModeDetector(fake);

        var state = await detector.DetectAsync(_tempDir, CancellationToken.None);

        Assert.Equal(InstallerMode.Config, state.Mode);
    }

    [Fact]
    public async Task Detect_Config_WhenInstalledIs_Newer_Than_Latest()
    {
        InstallManifestStore.Write(_tempDir,
            new InstallManifest("0.3.0", _tempDir, _tempDir, DateTimeOffset.UtcNow));

        var fake = new FakeReleaseClient
        {
            Release = new GiteaRelease("v0.2.0", "v0.2.0", Array.Empty<ReleaseAsset>())
        };
        var detector = new InstallModeDetector(fake);

        var state = await detector.DetectAsync(_tempDir, CancellationToken.None);

        Assert.Equal(InstallerMode.Config, state.Mode);
    }

    [Fact]
    public async Task Detect_Config_WhenInstalledVersion_IsUnparseable()
    {
        // install.json has been tampered with or written by an older installer with a
        // version string we can't compare. Must not crash; must land on Config (no update).
        InstallManifestStore.Write(_tempDir,
            new InstallManifest("garbage", _tempDir, _tempDir, DateTimeOffset.UtcNow));

        var fake = new FakeReleaseClient
        {
            Release = new GiteaRelease("v0.2.0", "v0.2.0", Array.Empty<ReleaseAsset>())
        };
        var detector = new InstallModeDetector(fake);

        var state = await detector.DetectAsync(_tempDir, CancellationToken.None);

        Assert.Equal(InstallerMode.Config, state.Mode);
    }
}
