using System;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace Электронная_Фармация.Classes
{
    public sealed class UpdateCheckResult
    {
        public bool CheckedOk { get; set; }
        public bool UpdateRequired { get; set; }
        public string LocalVersion { get; set; }
        public string LatestVersion { get; set; }
        public string ReleaseUrl { get; set; }
        public string DownloadUrl { get; set; }
        public string ErrorMessage { get; set; }
    }

    /// <summary>
    /// Checks Gitea Releases API for a newer desktop build.
    /// </summary>
    public static class UpdateChecker
    {
        private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
        {
            PropertyNameCaseInsensitive = true
        };

        public static UpdateCheckResult CheckForUpdate()
        {
            return CheckForUpdate(null, null, null, null);
        }

        /// <summary>
        /// Overload for tests / smoke scripts (null = use AppConfig).
        /// </summary>
        public static UpdateCheckResult CheckForUpdate(
            string baseUrl,
            string repo,
            string token,
            string localVersionOverride)
        {
            try
            {
                return CheckForUpdateAsync(baseUrl, repo, token, localVersionOverride)
                    .GetAwaiter().GetResult();
            }
            catch (Exception ex)
            {
                AppDebugLog.Info("UpdateChecker", $"Проверка обновлений пропущена: {ex.Message}");
                return new UpdateCheckResult
                {
                    CheckedOk = false,
                    UpdateRequired = false,
                    LocalVersion = localVersionOverride ?? GetLocalVersionString(),
                    ErrorMessage = ex.Message
                };
            }
        }

        public static async Task<UpdateCheckResult> CheckForUpdateAsync()
        {
            return await CheckForUpdateAsync(null, null, null, null).ConfigureAwait(false);
        }

        public static async Task<UpdateCheckResult> CheckForUpdateAsync(
            string baseUrlOverride,
            string repoOverride,
            string tokenOverride,
            string localVersionOverride)
        {
            var localVersion = string.IsNullOrWhiteSpace(localVersionOverride)
                ? GetLocalVersionString()
                : NormalizeVersion(localVersionOverride);
            var result = new UpdateCheckResult
            {
                LocalVersion = localVersion
            };

            var baseUrl = (baseUrlOverride ?? AppConfig.UpdateCheckBaseUrl).TrimEnd('/');
            var repo = (repoOverride ?? AppConfig.UpdateRepo).Trim('/');
            if (string.IsNullOrWhiteSpace(baseUrl) || string.IsNullOrWhiteSpace(repo))
            {
                result.CheckedOk = false;
                result.ErrorMessage = "UpdateCheckBaseUrl/UpdateRepo не заданы";
                return result;
            }

            var url = $"{baseUrl}/api/v1/repos/{repo}/releases/latest";
            using (var client = new HttpClient { Timeout = TimeSpan.FromSeconds(8) })
            {
                var token = tokenOverride ?? AppConfig.UpdateApiToken;
                if (!string.IsNullOrWhiteSpace(token))
                {
                    client.DefaultRequestHeaders.Authorization =
                        new AuthenticationHeaderValue("token", token.Trim());
                }

                client.DefaultRequestHeaders.Accept.Add(
                    new MediaTypeWithQualityHeaderValue("application/json"));

                AppDebugLog.Info("UpdateChecker", $"GET {url}");
                HttpResponseMessage response;
                try
                {
                    response = await client.GetAsync(url).ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    // Offline pharmacies must continue working.
                    AppDebugLog.Info("UpdateChecker", $"Сеть недоступна, обновление не блокируем: {ex.Message}");
                    result.CheckedOk = false;
                    result.ErrorMessage = ex.Message;
                    return result;
                }

                var body = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                if (!response.IsSuccessStatusCode)
                {
                    AppDebugLog.Info("UpdateChecker",
                        $"releases/latest HTTP {(int)response.StatusCode}: {Truncate(body, 200)}");
                    result.CheckedOk = false;
                    result.ErrorMessage = $"HTTP {(int)response.StatusCode}";
                    return result;
                }

                var release = JsonSerializer.Deserialize<GiteaRelease>(body, JsonOptions);
                if (release == null || string.IsNullOrWhiteSpace(release.TagName))
                {
                    result.CheckedOk = false;
                    result.ErrorMessage = "Пустой ответ releases/latest";
                    return result;
                }

                var latest = NormalizeVersion(release.TagName);
                result.LatestVersion = latest;
                result.ReleaseUrl = string.IsNullOrWhiteSpace(release.HtmlUrl)
                    ? $"{baseUrl}/{repo}/releases/tag/{release.TagName}"
                    : release.HtmlUrl;
                result.DownloadUrl = PickZipAssetUrl(release) ?? result.ReleaseUrl;
                result.CheckedOk = true;
                result.UpdateRequired = IsRemoteNewer(latest, localVersion);

                AppDebugLog.Info("UpdateChecker",
                    $"Локально={localVersion}, latest={latest}, updateRequired={result.UpdateRequired}");
                return result;
            }
        }

        public static string GetLocalVersionString()
        {
            try
            {
                var asm = Assembly.GetExecutingAssembly();
                var file = asm.GetCustomAttribute<AssemblyFileVersionAttribute>()?.Version;
                if (!string.IsNullOrWhiteSpace(file))
                {
                    return NormalizeVersion(file);
                }

                return NormalizeVersion(asm.GetName().Version?.ToString() ?? "0.0.0.0");
            }
            catch
            {
                return "0.0.0.0";
            }
        }

        /// <summary>
        /// Returns true when remoteTag is strictly newer than localVersion.
        /// Accepts "v1.0.9", "1.0.9", "1.0.9.0".
        /// </summary>
        public static bool IsRemoteNewer(string remoteTag, string localVersion)
        {
            if (!TryParseVersion(remoteTag, out var remote) ||
                !TryParseVersion(localVersion, out var local))
            {
                return false;
            }

            return remote > local;
        }

        public static string NormalizeVersion(string value)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                return "0.0.0.0";
            }

            value = value.Trim();
            if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase))
            {
                value = value.Substring(1);
            }

            // Drop pre-release suffix: 1.0.9-beta -> 1.0.9
            var dash = value.IndexOf('-');
            if (dash > 0)
            {
                value = value.Substring(0, dash);
            }

            if (TryParseVersion(value, out var v))
            {
                return v.ToString();
            }

            return value;
        }

        public static bool TryParseVersion(string value, out Version version)
        {
            version = null;
            if (string.IsNullOrWhiteSpace(value))
            {
                return false;
            }

            value = value.Trim();
            if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase))
            {
                value = value.Substring(1);
            }

            var dash = value.IndexOf('-');
            if (dash > 0)
            {
                value = value.Substring(0, dash);
            }

            var parts = value.Split('.');
            if (parts.Length == 1)
            {
                value = value + ".0.0.0";
            }
            else if (parts.Length == 2)
            {
                value = value + ".0.0";
            }
            else if (parts.Length == 3)
            {
                value = value + ".0";
            }

            return Version.TryParse(value, out version);
        }

        private static string PickZipAssetUrl(GiteaRelease release)
        {
            if (release?.Assets == null || release.Assets.Length == 0)
            {
                return null;
            }

            var zip = release.Assets.FirstOrDefault(a =>
                !string.IsNullOrWhiteSpace(a?.Name) &&
                a.Name.EndsWith(".zip", StringComparison.OrdinalIgnoreCase));
            if (zip == null)
            {
                zip = release.Assets.FirstOrDefault(a => !string.IsNullOrWhiteSpace(a?.BrowserDownloadUrl));
            }

            return zip?.BrowserDownloadUrl;
        }

        private static string Truncate(string s, int max)
        {
            if (string.IsNullOrEmpty(s) || s.Length <= max)
            {
                return s ?? string.Empty;
            }

            return s.Substring(0, max) + "...";
        }

        private sealed class GiteaRelease
        {
            [JsonPropertyName("tag_name")]
            public string TagName { get; set; }

            [JsonPropertyName("html_url")]
            public string HtmlUrl { get; set; }

            [JsonPropertyName("assets")]
            public GiteaAsset[] Assets { get; set; }
        }

        private sealed class GiteaAsset
        {
            [JsonPropertyName("name")]
            public string Name { get; set; }

            [JsonPropertyName("browser_download_url")]
            public string BrowserDownloadUrl { get; set; }
        }
    }
}
