using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

namespace Электронная_Фармация.Classes
{
    /// <summary>
    /// Downloads a Gitea release ZIP and applies it via an external batch updater
    /// so the running exe can be replaced. Preserves local databases and Logs.
    /// </summary>
    public static class AutoUpdater
    {
        public static async Task ApplyUpdateAsync(
            UpdateCheckResult update,
            IProgress<string> status = null)
        {
            if (update == null)
            {
                throw new ArgumentNullException(nameof(update));
            }

            var downloadUrl = !string.IsNullOrWhiteSpace(update.DownloadUrl)
                ? update.DownloadUrl
                : update.ReleaseUrl;
            if (string.IsNullOrWhiteSpace(downloadUrl) ||
                !downloadUrl.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
            {
                // Prefer zipball if asset missing
                if (!string.IsNullOrWhiteSpace(downloadUrl) &&
                    !downloadUrl.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
                {
                    throw new InvalidOperationException(
                        "В релизе нет ZIP-сборки. Прикрепите ElectronicPharmacy-vX.Y.Z.zip к Release.");
                }

                throw new InvalidOperationException("Нет ссылки на ZIP обновления.");
            }

            var installDir = AppDomain.CurrentDomain.BaseDirectory.TrimEnd(
                Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
            var currentExe = Process.GetCurrentProcess().MainModule?.FileName;
            if (string.IsNullOrWhiteSpace(currentExe) || !File.Exists(currentExe))
            {
                currentExe = Directory.GetFiles(installDir, "*.exe")
                    .FirstOrDefault(p => !p.EndsWith(".vshost.exe", StringComparison.OrdinalIgnoreCase));
            }

            if (string.IsNullOrWhiteSpace(currentExe))
            {
                throw new InvalidOperationException("Не найден текущий exe.");
            }

            var workRoot = Path.Combine(Path.GetTempPath(), "elfisa-update-" + Guid.NewGuid().ToString("N"));
            var zipPath = Path.Combine(workRoot, "update.zip");
            var extractDir = Path.Combine(workRoot, "extract");
            Directory.CreateDirectory(extractDir);

            try
            {
                Report(status, "Скачивание обновления...");
                await DownloadAsync(downloadUrl, zipPath).ConfigureAwait(false);

                Report(status, "Распаковка...");
                ZipFile.ExtractToDirectory(zipPath, extractDir);

                var payloadDir = FindPayloadDirectory(extractDir);
                if (payloadDir == null)
                {
                    throw new InvalidOperationException("В архиве не найден exe приложения.");
                }

                var updaterBat = Path.Combine(workRoot, "apply-update.cmd");
                WriteUpdaterScript(updaterBat);

                Report(status, "Подготовка перезапуска...");
                var psi = new ProcessStartInfo
                {
                    FileName = updaterBat,
                    Arguments = Quote(payloadDir) + " " + Quote(installDir) + " " +
                                Quote(Path.GetFileName(currentExe)) + " " +
                                Process.GetCurrentProcess().Id,
                    UseShellExecute = true,
                    WindowStyle = ProcessWindowStyle.Hidden,
                    WorkingDirectory = workRoot,
                    CreateNoWindow = true
                };
                Process.Start(psi);
                AppDebugLog.Info("AutoUpdater",
                    $"Updater started. payload={payloadDir}, install={installDir}, ver={update.LatestVersion}");
            }
            catch
            {
                try
                {
                    if (Directory.Exists(workRoot))
                    {
                        Directory.Delete(workRoot, true);
                    }
                }
                catch
                {
                    // ignore cleanup errors
                }

                throw;
            }
        }

        private static async Task DownloadAsync(string url, string zipPath)
        {
            using (var client = new HttpClient { Timeout = TimeSpan.FromMinutes(10) })
            {
                var token = AppConfig.UpdateApiToken;
                if (!string.IsNullOrWhiteSpace(token))
                {
                    client.DefaultRequestHeaders.Authorization =
                        new AuthenticationHeaderValue("token", token.Trim());
                }

                using (var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead)
                           .ConfigureAwait(false))
                {
                    response.EnsureSuccessStatusCode();
                    using (var fs = new FileStream(zipPath, FileMode.Create, FileAccess.Write, FileShare.None))
                    {
                        await response.Content.CopyToAsync(fs).ConfigureAwait(false);
                    }
                }
            }
        }

        private static string FindPayloadDirectory(string extractRoot)
        {
            // ZIP may contain a single root folder (Compress-Archive of a directory).
            var exe = Directory.GetFiles(extractRoot, "*.exe", SearchOption.AllDirectories)
                .FirstOrDefault(p =>
                {
                    var name = Path.GetFileName(p) ?? string.Empty;
                    return !name.EndsWith(".vshost.exe", StringComparison.OrdinalIgnoreCase) &&
                           !name.Equals("UpdateCheckSmoke.exe", StringComparison.OrdinalIgnoreCase);
                });

            return exe == null ? null : Path.GetDirectoryName(exe);
        }

        private static void WriteUpdaterScript(string path)
        {
            // Waits for app exit (max ~30s, then force-kill), copies files, keeps DB/Logs, restarts.
            var sb = new StringBuilder();
            sb.AppendLine("@echo off");
            sb.AppendLine("setlocal EnableDelayedExpansion");
            sb.AppendLine("set \"SRC=%~1\"");
            sb.AppendLine("set \"DST=%~2\"");
            sb.AppendLine("set \"EXENAME=%~3\"");
            sb.AppendLine("set \"PID=%~4\"");
            sb.AppendLine("set \"N=0\"");
            sb.AppendLine("echo Waiting for PID %PID% ... > \"%TEMP%\\elfisa-updater.log\"");
            sb.AppendLine(":waitloop");
            sb.AppendLine("tasklist /FI \"PID eq %PID%\" 2>nul | findstr /C:\" %PID% \" >nul");
            sb.AppendLine("if errorlevel 1 goto donewait");
            sb.AppendLine("set /a N+=1");
            sb.AppendLine("echo still alive n=!N! >> \"%TEMP%\\elfisa-updater.log\"");
            sb.AppendLine("if !N! GEQ 30 (");
            sb.AppendLine("  echo force kill %PID% >> \"%TEMP%\\elfisa-updater.log\"");
            sb.AppendLine("  taskkill /PID %PID% /F >nul 2>&1");
            sb.AppendLine("  goto donewait");
            sb.AppendLine(")");
            // ping instead of timeout — timeout often hangs in hidden windows
            sb.AppendLine("ping 127.0.0.1 -n 2 >nul");
            sb.AppendLine("goto waitloop");
            sb.AppendLine(":donewait");
            sb.AppendLine("ping 127.0.0.1 -n 2 >nul");
            sb.AppendLine("echo Copying update... >> \"%TEMP%\\elfisa-updater.log\"");
            sb.AppendLine("robocopy \"%SRC%\" \"%DST%\" /E /R:2 /W:1 /NFL /NDL /NJH /NJS /XF efClient.db *.db /XD Logs");
            sb.AppendLine("set \"RC=!ERRORLEVEL!\"");
            sb.AppendLine("echo robocopy=!RC! >> \"%TEMP%\\elfisa-updater.log\"");
            sb.AppendLine("echo Starting \"%DST%\\%EXENAME%\" >> \"%TEMP%\\elfisa-updater.log\"");
            sb.AppendLine("start \"\" /D \"%DST%\" \"%DST%\\%EXENAME%\"");
            sb.AppendLine("exit /b 0");
            File.WriteAllText(path, sb.ToString(), Encoding.Default);
        }

        private static string Quote(string value)
        {
            return "\"" + (value ?? string.Empty).Replace("\"", string.Empty) + "\"";
        }

        private static void Report(IProgress<string> status, string message)
        {
            AppDebugLog.Info("AutoUpdater", message);
            status?.Report(message);
        }
    }
}
