using System.Net.Http;
using System.Text.Json;

namespace Elfisa.Core;

/// <summary>Результат проверки обновления.</summary>
public sealed class UpdateInfo
{
    public bool Available { get; init; }
    public string CurrentVersion { get; init; } = "";
    public string LatestVersion { get; init; } = "";
    public string ReleaseUrl { get; init; } = "";
    public string SetupUrl { get; init; } = "";
}

/// <summary>
/// Проверка и скачивание обновлений Avalonia-клиента.
/// Смотрит в Gitea Releases (тот же репозиторий, что и старый WinForms-апдейтер),
/// установщик тянет с CDN. Никаких токенов — репозиторий и CDN публичны.
/// </summary>
public static class UpdateService
{
    // Первичный источник — публичный CDN (тот же хост, откуда качается установщик;
    // надёжнее приватных API и отдельных поддоменов). Fallback — Gitea Releases.
    private const string LatestJsonUrl = "https://cdn.24pharmdata.ru/latest.json";
    private const string ReleasesLatest =
        "https://git.24pharmdata.ru/api/v1/repos/pharmdata/elfisa-pharmacy/releases/latest";

    /// <summary>Стабильный URL установщика на CDN (всегда последняя версия).</summary>
    public const string SetupUrl = "https://cdn.24pharmdata.ru/ElfisaPharmacy-Setup.exe";

    /// <summary>Проверяет наличие новой версии. Возвращает null при офлайне/ошибке (не мешаем работе).</summary>
    public static async Task<UpdateInfo?> CheckAsync(Version current)
    {
        var info = await TryCdnAsync(current).ConfigureAwait(false)
                   ?? await TryGiteaAsync(current).ConfigureAwait(false);
        Log(info is null
            ? $"current={current}: проверка не удалась (нет ответа CDN и Gitea)"
            : $"current={current} latest={info.LatestVersion} available={info.Available}");
        return info;
    }

    // CDN latest.json: {"version":"2.0.7","setup":"https://cdn.24pharmdata.ru/ElfisaPharmacy-Setup.exe"}
    private static async Task<UpdateInfo?> TryCdnAsync(Version current)
    {
        try
        {
            using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(8) };
            var json = await http.GetStringAsync(LatestJsonUrl).ConfigureAwait(false);
            using var doc = JsonDocument.Parse(json);
            var root = doc.RootElement;
            var ver = root.TryGetProperty("version", out var v) ? v.GetString() : null;
            var latest = ParseVersion(ver);
            if (latest is null) return null;
            var setup = root.TryGetProperty("setup", out var s) ? s.GetString() : null;
            return new UpdateInfo
            {
                Available = latest > current,
                CurrentVersion = current.ToString(),
                LatestVersion = latest.ToString(),
                ReleaseUrl = "",
                SetupUrl = string.IsNullOrWhiteSpace(setup) ? SetupUrl : setup!,
            };
        }
        catch (Exception ex) { Log("CDN latest.json недоступен: " + ex.Message); return null; }
    }

    private static async Task<UpdateInfo?> TryGiteaAsync(Version current)
    {
        try
        {
            using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(8) };
            http.DefaultRequestHeaders.Add("Accept", "application/json");
            var json = await http.GetStringAsync(ReleasesLatest).ConfigureAwait(false);
            using var doc = JsonDocument.Parse(json);
            var root = doc.RootElement;
            var tag = root.TryGetProperty("tag_name", out var t) ? t.GetString() : null;
            var latest = ParseVersion(tag);
            if (latest is null) return null;
            var htmlUrl = root.TryGetProperty("html_url", out var h) ? h.GetString() : null;
            return new UpdateInfo
            {
                Available = latest > current,
                CurrentVersion = current.ToString(),
                LatestVersion = latest.ToString(),
                ReleaseUrl = htmlUrl ?? "",
                SetupUrl = SetupUrl,
            };
        }
        catch (Exception ex) { Log("Gitea releases/latest недоступен: " + ex.Message); return null; }
    }

    /// <summary>Лог проверки в %TEMP%\elfisa-update.log — для диагностики на машине клиента.</summary>
    private static void Log(string msg)
    {
        try
        {
            var path = Path.Combine(Path.GetTempPath(), "elfisa-update.log");
            File.AppendAllText(path, $"{DateTime.Now:yyyy-MM-dd HH:mm:ss}  {msg}{Environment.NewLine}");
        }
        catch { /* лог не критичен */ }
    }

    /// <summary>Скачивает установщик с CDN во временный файл. Возвращает путь.</summary>
    public static async Task<string> DownloadSetupAsync(IProgress<double>? progress, CancellationToken ct = default)
    {
        var path = Path.Combine(Path.GetTempPath(), "ElfisaPharmacy-Setup.exe");
        using var http = new HttpClient { Timeout = TimeSpan.FromMinutes(10) };
        using var resp = await http.GetAsync(SetupUrl, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false);
        resp.EnsureSuccessStatusCode();
        var total = resp.Content.Headers.ContentLength ?? -1;
        await using var src = await resp.Content.ReadAsStreamAsync(ct).ConfigureAwait(false);
        await using var dst = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
        var buffer = new byte[81920];
        long readTotal = 0;
        int n;
        while ((n = await src.ReadAsync(buffer, ct).ConfigureAwait(false)) > 0)
        {
            await dst.WriteAsync(buffer.AsMemory(0, n), ct).ConfigureAwait(false);
            readTotal += n;
            if (total > 0) progress?.Report(readTotal * 100.0 / total);
        }
        return path;
    }

    /// <summary>Разбирает тег вида v2.0.4 / 2.0.4 / 2.0.4-beta в Version.</summary>
    internal static Version? ParseVersion(string? tag)
    {
        if (string.IsNullOrWhiteSpace(tag)) return null;
        tag = tag.Trim();
        if (tag.StartsWith("v", StringComparison.OrdinalIgnoreCase)) tag = tag[1..];
        var dash = tag.IndexOf('-');
        if (dash > 0) tag = tag[..dash];
        switch (tag.Split('.').Length)
        {
            case 1: tag += ".0.0.0"; break;
            case 2: tag += ".0.0"; break;
            case 3: tag += ".0"; break;
        }
        return Version.TryParse(tag, out var v) ? v : null;
    }
}
