using System.IO;
using System.Net.Http;
using System.Text.RegularExpressions;

namespace ClaudeDo.Releases;

public static partial class SelfUpdater
{
    [GeneratedRegex(@"^ClaudeDo\.Installer-(?<version>[\d\.]+)\.exe$", RegexOptions.IgnoreCase)]
    private static partial Regex InstallerAssetRegex();

    public static InstallerAssetMatch? FindInstallerAsset(IEnumerable<ReleaseAsset> assets)
    {
        foreach (var asset in assets)
        {
            var m = InstallerAssetRegex().Match(asset.Name);
            if (m.Success)
            {
                return new InstallerAssetMatch(asset, m.Groups["version"].Value);
            }
        }
        return null;
    }

    public static async Task<SelfUpdateDecision> DecideUpdateAsync(
        IReleaseClient releases,
        string currentVersion,
        CancellationToken ct)
    {
        GiteaRelease? release;
        try
        {
            release = await releases.GetLatestReleaseAsync(ct);
        }
        catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException)
        {
            return new SelfUpdateDecision(SelfUpdateDecisionKind.NoUpdate);
        }

        if (release is null)
            return new SelfUpdateDecision(SelfUpdateDecisionKind.NoUpdate);

        var match = FindInstallerAsset(release.Assets);
        if (match is null)
            return new SelfUpdateDecision(SelfUpdateDecisionKind.NoUpdate);

        var cmp = VersionComparer.Compare(match.Version, currentVersion);
        if (!cmp.IsNewer)
            return new SelfUpdateDecision(SelfUpdateDecisionKind.NoUpdate);

        var checksums = release.Assets.FirstOrDefault(
            a => string.Equals(a.Name, "checksums.txt", StringComparison.OrdinalIgnoreCase));

        return new SelfUpdateDecision(
            SelfUpdateDecisionKind.UpdateAvailable,
            LatestVersion: match.Version,
            InstallerAsset: match.Asset,
            ChecksumsAsset: checksums);
    }

    public static async Task<bool> HandleReplaceSelfAsync(
        string oldPath,
        string currentExePath,
        Func<string, bool> launchProcess,
        int maxWaitMs = 5000)
    {
        var deadline = DateTime.UtcNow.AddMilliseconds(maxWaitMs);
        while (DateTime.UtcNow < deadline)
        {
            try
            {
                if (File.Exists(oldPath))
                {
                    File.Delete(oldPath);
                }
                break;
            }
            catch (IOException)
            {
                await Task.Delay(100);
            }
            catch (UnauthorizedAccessException)
            {
                await Task.Delay(100);
            }
        }

        if (File.Exists(oldPath))
        {
            return false;
        }

        File.Copy(currentExePath, oldPath, overwrite: false);
        return launchProcess(oldPath);
    }

    public static async Task<string?> DownloadAndVerifyAsync(
        IReleaseClient releases,
        ReleaseAsset installerAsset,
        ReleaseAsset checksumsAsset,
        string tempDir,
        IProgress<long> progress,
        CancellationToken ct)
    {
        Directory.CreateDirectory(tempDir);
        var installerPath = Path.Combine(tempDir, installerAsset.Name);
        var checksumsPath = Path.Combine(tempDir, "checksums.txt");

        try
        {
            await releases.DownloadAsync(installerAsset.BrowserDownloadUrl, installerPath, progress, ct);
            await releases.DownloadAsync(checksumsAsset.BrowserDownloadUrl, checksumsPath, new Progress<long>(_ => { }), ct);
        }
        catch (Exception ex) when (ex is HttpRequestException or IOException or TaskCanceledException)
        {
            return null;
        }

        var checksumsText = await File.ReadAllTextAsync(checksumsPath, ct);
        var map = ChecksumVerifier.ParseChecksumsFile(checksumsText);
        if (!map.TryGetValue(installerAsset.Name, out var expected))
            return null;

        return ChecksumVerifier.Verify(installerPath, expected) ? installerPath : null;
    }
}
