using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SQLite;
using System.Drawing;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Электронная_Фармация.Classes;
using Электронная_Фармация.Properties;

namespace Электронная_Фармация.HelpForms
{
    public partial class HF_DownloadDataFromServer : Form
    {
        // Доли общего прогресса 0..100.
        private const int DownloadPhaseEnd = 45;
        private const int BuildPhaseEnd = 55;
        private const int SavePhaseEnd = 100;

        public HF_DownloadDataFromServer()
        {
            InitializeComponent();
        }

        // Чтобы при обновлении прайса не терять корзины других аптек,
        // чистим TempOrderItems только для активного грузополучателя.
        public string ActiveConsigneeName { get; set; } = string.Empty;

        private readonly string _login = Settings.Default.stringLogin ?? string.Empty;
        private readonly string _password = Settings.Default.stringPassword ?? string.Empty;
        private readonly string _connectionStringToLocalDb = AppConfig.SqliteConnectionString;
        private bool _isDownloading;

        private async void HF_DownloadDataFromServer_Load(object sender, EventArgs e)
        {
            UiThemeHelper.ApplyToControlTree(this);
            ConfigureLogView();
            btnCloseThis.Enabled = false;
            dataGridView1.Visible = false;
            rtxtDebug.Visible = true;
            rtxtDebug.ReadOnly = true;
            _isDownloading = true;
            SetOverallProgress(0, "Подключение к серверу...");

            var client = new ApiClient();
            AppendStatus($"Сервер: {client.BaseUrl}");
            AppendStatus($"Лог: {AppDebugLog.CurrentLogFilePath}");
            AppDebugLog.Info("Download", $"Старт загрузки прайса. Сервер={client.BaseUrl}");

            try
            {
                SetOverallProgress(1, "Авторизация...");
                await EnsureAuthenticatedAsync(client);

                try
                {
                    AppendStatus("Загружаю грузополучателей с сервера...");
                    var locations = await client.GetBuyerLocationsAsync();
                    var synced = ConsigneeHelper.SyncFromServer(locations);
                    AppendStatus(synced > 0
                        ? $"Грузополучателей обновлено: {synced}"
                        : "Грузополучателей на сервере пока нет");
                }
                catch (Exception locEx)
                {
                    AppendStatus($"Грузополучатели не обновлены: {locEx.Message}");
                    AppDebugLog.Error("Download", "Не удалось синхронизировать грузополучателей", locEx);
                }

                SetOverallProgress(3, "Загрузка прайса с сервера...");
                AppendStatus("Загружаю сводный прайс...");
                var allSuppliersSummary = await GetFullPriceSummaryPagedAsync(client);
                int totalItems = allSuppliersSummary.Summary?.Count ?? 0;

                // Наценки прайса/региона/покупателя считает сервер.
                // Десктоп сохраняет цену из API как есть, без локального пересчёта.
                AppendStatus("Цена берётся с сервера без локального пересчёта наценок");

                AppendStatus($"Получено {totalItems} позиций");
                SetOverallProgress(DownloadPhaseEnd, $"Получено {totalItems} / {totalItems} позиций (100%)");

                SetOverallProgress(DownloadPhaseEnd, "Подготовка таблицы...");
                var buildProgress = new Progress<CountProgress>(p =>
                {
                    int phasePercent = MapPhasePercent(p.Current, p.Total, DownloadPhaseEnd, BuildPhaseEnd);
                    SetOverallProgress(
                        phasePercent,
                        $"Обработка: {p.Current} / {p.Total} ({PercentOf(p.Current, p.Total)}%)");
                });
                var tablePrice = await Task.Run(() => BuildPriceTable(allSuppliersSummary, buildProgress));

                SetOverallProgress(BuildPhaseEnd, "Сохранение в локальную базу...");
                var saveProgress = new Progress<CountProgress>(p =>
                {
                    int phasePercent = MapPhasePercent(p.Current, p.Total, BuildPhaseEnd, SavePhaseEnd);
                    SetOverallProgress(
                        phasePercent,
                        $"Сохранение: {p.Current} / {p.Total} ({PercentOf(p.Current, p.Total)}%)");
                });
                await Task.Run(() => SavePriceTable(tablePrice, saveProgress));

                SetOverallProgress(100, $"Готово: {tablePrice.Rows.Count} / {tablePrice.Rows.Count} (100%)");
                DialogResult = DialogResult.OK;
                AppendStatus($"Готово. Сохранено {tablePrice.Rows.Count} позиций в локальную базу.");
                ToastNotification.ShowSuccess($"Прайс обновлён: {tablePrice.Rows.Count} позиций");

                Settings.Default.LastPriceUpdateUtc = DateTime.UtcNow;
                Settings.Default.LastPriceReminderUtc = DateTime.MinValue;
                Settings.Default.Save();
            }
            catch (UnauthorizedAccessException ex)
            {
                SetOverallProgress(0, "Ошибка авторизации");
                AppendStatus($"Ошибка авторизации: {ex.Message}");
                AppDebugLog.Error("Download", "Ошибка авторизации при загрузке прайса", ex);
                ToastNotification.ShowError($"Ошибка авторизации: {ex.Message}");
            }
            catch (HttpRequestException ex)
            {
                SetOverallProgress(0, "Ошибка сети");
                AppendStatus($"Ошибка сети: {ex.Message}");
                AppDebugLog.Error("Download", "Ошибка сети при загрузке прайса", ex);
                ToastNotification.ShowError($"Ошибка сети: {ex.Message}");
            }
            catch (Exception ex)
            {
                SetOverallProgress(0, "Ошибка загрузки");
                AppendStatus($"Ошибка загрузки прайса: {ex.Message}");
                AppDebugLog.Error("Download", "Неожиданная ошибка при загрузке прайса", ex);
                ToastNotification.ShowError($"Ошибка загрузки прайса: {ex.Message}");
            }
            finally
            {
                _isDownloading = false;
                btnCloseThis.Enabled = true;
            }
        }

        private async Task EnsureAuthenticatedAsync(ApiClient client)
        {
            if (!string.IsNullOrWhiteSpace(Settings.Default.stringToken))
            {
                client.SetToken(Settings.Default.stringToken);
                AppendStatus("Используется сохранённый токен...");
                return;
            }

            await LoginAndSaveTokenAsync(client);
        }

        private async Task LoginAndSaveTokenAsync(ApiClient client)
        {
            if (string.IsNullOrWhiteSpace(_login) || string.IsNullOrWhiteSpace(_password))
            {
                throw new UnauthorizedAccessException(
                    "Не заданы логин и пароль. Выполните авторизацию в меню «Регистрация».");
            }

            AppendStatus("Выполняю авторизацию...");
            var token = await client.LoginAsync(_login, _password);
            Settings.Default.stringToken = token;
            Settings.Default.Save();
            AppendStatus("Авторизация успешно пройдена");
        }

        private async Task<PriceSummaryResponse> GetFullPriceSummaryPagedAsync(ApiClient client)
        {
            var regionId = string.IsNullOrWhiteSpace(Settings.Default.RegionId)
                ? null
                : Settings.Default.RegionId;

            const int supplierPageLimit = 100000;
            const int discoveryLimit = 50000;

            var combined = new PriceSummaryResponse { Summary = new List<PriceSummaryItem>() };
            var transferProgress = new Progress<HttpTransferProgress>(ReportTransferProgress);

            var supplierIds = new List<string>(ConsigneeHelper.GetKnownSupplierIds());
            if (supplierIds.Count == 0)
            {
                AppendStatus("Первая загрузка: определяю список поставщиков...");
                SetOverallProgress(3, "Определение поставщиков...");
                var discovery = await GetPriceSummaryWithAuthRetryAsync(
                    client,
                    supplierId: null,
                    regionId: regionId,
                    limit: discoveryLimit,
                    offset: 0,
                    transferProgress: transferProgress);

                if (discovery?.MarkupPercent.HasValue == true)
                {
                    combined.MarkupPercent = discovery.MarkupPercent;
                }

                var discovered = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
                if (discovery?.Summary != null)
                {
                    foreach (var item in discovery.Summary)
                    {
                        if (!string.IsNullOrWhiteSpace(item.SupplierId))
                        {
                            discovered.Add(item.SupplierId.Trim());
                        }
                    }
                }

                supplierIds.AddRange(discovered);
                AppendStatus($"Найдено поставщиков: {supplierIds.Count}");
            }

            if (supplierIds.Count == 0)
            {
                AppendStatus("Поставщики не найдены, пробую полный запрос...");
                var fallback = await GetPriceSummaryWithAuthRetryAsync(
                    client,
                    supplierId: null,
                    regionId: regionId,
                    limit: 200000,
                    offset: 0,
                    transferProgress: transferProgress);
                if (fallback?.MarkupPercent.HasValue == true)
                {
                    combined.MarkupPercent = fallback.MarkupPercent;
                }

                if (fallback?.Summary != null)
                {
                    combined.Summary.AddRange(fallback.Summary);
                }

                return combined;
            }

            int totalSuppliers = supplierIds.Count;
            for (int i = 0; i < totalSuppliers; i++)
            {
                var supplierId = supplierIds[i];
                int supplierProgressStart = 3 + (int)((DownloadPhaseEnd - 3) * ((double)i / totalSuppliers));
                int supplierProgressEnd = 3 + (int)((DownloadPhaseEnd - 3) * ((double)(i + 1) / totalSuppliers));
                SetOverallProgress(
                    supplierProgressStart,
                    $"Поставщик {i + 1}/{totalSuppliers}...");
                AppendStatus($"Загрузка поставщика {i + 1}/{totalSuppliers}: {supplierId}");

                PriceSummaryResponse chunk;
                try
                {
                    chunk = await GetPriceSummaryWithAuthRetryAsync(
                        client,
                        supplierId: supplierId,
                        regionId: regionId,
                        limit: supplierPageLimit,
                        offset: 0,
                        transferProgress: transferProgress);
                }
                catch (Exception ex)
                {
                    AppendStatus($"Ошибка поставщика {supplierId}: {ex.Message}");
                    AppDebugLog.Error("Download", $"Ошибка загрузки прайса поставщика {supplierId}", ex);
                    continue;
                }

                if (i == 0 && chunk?.MarkupPercent.HasValue == true && !combined.MarkupPercent.HasValue)
                {
                    combined.MarkupPercent = chunk.MarkupPercent;
                }

                int got = chunk?.Summary?.Count ?? 0;
                if (got > 0)
                {
                    combined.Summary.AddRange(chunk.Summary);
                }

                SetOverallProgress(
                    Math.Max(supplierProgressStart, supplierProgressEnd - 1),
                    $"Поставщик {i + 1}/{totalSuppliers}: +{got}, всего {combined.Summary.Count}");
                AppendStatus($"Поставщик {i + 1}/{totalSuppliers}: получено {got}, всего {combined.Summary.Count}");
            }

            // Лёгкий discovery новых поставщиков, которых ещё не было в локальной базе.
            try
            {
                AppendStatus("Проверка новых поставщиков...");
                var probe = await GetPriceSummaryWithAuthRetryAsync(
                    client,
                    supplierId: null,
                    regionId: regionId,
                    limit: 5000,
                    offset: 0,
                    transferProgress: null);
                var known = new HashSet<string>(supplierIds, StringComparer.OrdinalIgnoreCase);
                var newcomers = new List<string>();
                if (probe?.Summary != null)
                {
                    foreach (var item in probe.Summary)
                    {
                        if (!string.IsNullOrWhiteSpace(item.SupplierId) && known.Add(item.SupplierId.Trim()))
                        {
                            newcomers.Add(item.SupplierId.Trim());
                        }
                    }
                }

                for (int i = 0; i < newcomers.Count; i++)
                {
                    var supplierId = newcomers[i];
                    AppendStatus($"Новый поставщик {i + 1}/{newcomers.Count}: {supplierId}");
                    var chunk = await GetPriceSummaryWithAuthRetryAsync(
                        client,
                        supplierId: supplierId,
                        regionId: regionId,
                        limit: supplierPageLimit,
                        offset: 0,
                        transferProgress: transferProgress);
                    int got = chunk?.Summary?.Count ?? 0;
                    if (got > 0)
                    {
                        combined.Summary.AddRange(chunk.Summary);
                    }

                    AppendStatus($"Новый поставщик: +{got}, всего {combined.Summary.Count}");
                }
            }
            catch (Exception ex)
            {
                AppendStatus($"Проверка новых поставщиков пропущена: {ex.Message}");
                AppDebugLog.Error("Download", "Ошибка discovery новых поставщиков", ex);
            }

            return combined;
        }

        private async Task<PriceSummaryResponse> GetPriceSummaryWithAuthRetryAsync(
            ApiClient client,
            string supplierId,
            string regionId,
            int limit,
            int offset,
            IProgress<HttpTransferProgress> transferProgress)
        {
            try
            {
                return await client.GetPriceSummaryAsync(
                    supplierId: supplierId,
                    regionId: regionId,
                    limit: limit,
                    offset: offset,
                    transferProgress: transferProgress);
            }
            catch (UnauthorizedAccessException)
            {
                AppendStatus("Токен недействителен, повторная авторизация...");
                SetOverallProgress(2, "Повторная авторизация...");
                Settings.Default.stringToken = string.Empty;
                Settings.Default.Save();
                client.SetToken(null);
                await LoginAndSaveTokenAsync(client);
                return await client.GetPriceSummaryAsync(
                    supplierId: supplierId,
                    regionId: regionId,
                    limit: limit,
                    offset: offset,
                    transferProgress: transferProgress);
            }
        }

        private void ReportTransferProgress(HttpTransferProgress transfer)
        {
            if (transfer.TotalBytes.HasValue && transfer.TotalBytes.Value > 0)
            {
                int phasePercent = MapPhasePercent(transfer.BytesReceived, transfer.TotalBytes.Value, 3, DownloadPhaseEnd);
                SetOverallProgress(
                    phasePercent,
                    $"Скачивание: {FormatBytes(transfer.BytesReceived)} / {FormatBytes(transfer.TotalBytes.Value)} ({transfer.Percent}%)");
            }
            else
            {
                SetOverallProgress(
                    Math.Min(DownloadPhaseEnd - 1, 5),
                    $"Скачивание: получено {FormatBytes(transfer.BytesReceived)}...");
            }
        }

        private static DataTable BuildPriceTable(
            PriceSummaryResponse allSuppliersSummary,
            IProgress<CountProgress> progress = null)
        {
            var tablePrice = new DataTable();
            tablePrice.Columns.Add("supplier_price_id");
            tablePrice.Columns.Add("guid_es");
            tablePrice.Columns.Add("es_code");
            tablePrice.Columns.Add("supplier_id");
            tablePrice.Columns.Add("DrugName");
            tablePrice.Columns.Add("SupplierName");
            tablePrice.Columns.Add("Price");
            tablePrice.Columns.Add("Quantity");
            tablePrice.Columns.Add(new DataColumn
            {
                ColumnName = "ExpiryPeriod",
                DataType = typeof(string),
                AllowDBNull = true
            });
            tablePrice.Columns.Add(new DataColumn
            {
                ColumnName = "Description",
                DataType = typeof(string),
                AllowDBNull = true
            });
            tablePrice.Columns.Add("Zakaz");
            tablePrice.Columns.Add("SummaZakaza");
            tablePrice.Columns.Add(new DataColumn
            {
                ColumnName = "TradeName",
                DataType = typeof(string),
                AllowDBNull = true
            });
            tablePrice.Columns.Add(new DataColumn
            {
                ColumnName = "Dosage",
                DataType = typeof(string),
                AllowDBNull = true
            });
            tablePrice.Columns.Add(new DataColumn
            {
                ColumnName = "MarkupPercent",
                DataType = typeof(decimal),
                AllowDBNull = true
            });

            if (allSuppliersSummary?.Summary == null)
            {
                return tablePrice;
            }

            int total = allSuppliersSummary.Summary.Count;
            int current = 0;
            foreach (var item in allSuppliersSummary.Summary)
            {
                var row = tablePrice.NewRow();
                row["supplier_price_id"] = item.SupplierPriceID ?? string.Empty;
                row["guid_es"] = item.GuidEs ?? string.Empty;
                row["es_code"] = item.EsCode?.ToString() ?? "0";
                row["supplier_id"] = item.SupplierId ?? string.Empty;
                row["DrugName"] = item.DrugName ?? string.Empty;
                row["SupplierName"] = item.SupplierName ?? string.Empty;

                // Цена уже итоговая с сервера — локально не пересчитываем.
                var serverPrice = item.Price ?? 0m;
                row["Price"] = serverPrice.ToString(System.Globalization.CultureInfo.InvariantCulture);
                row["Quantity"] = item.Quantity?.ToString() ?? string.Empty;
                row["ExpiryPeriod"] = item.ExpiryPeriod == null ? (object)DBNull.Value : item.ExpiryPeriod;
                row["Description"] = item.Description == null ? (object)DBNull.Value : item.Description;
                row["Zakaz"] = string.Empty;
                row["SummaZakaza"] = string.Empty;
                row["TradeName"] = string.IsNullOrWhiteSpace(item.TradeName) ? (object)DBNull.Value : item.TradeName.Trim();
                row["Dosage"] = string.IsNullOrWhiteSpace(item.Dosage) ? (object)DBNull.Value : item.Dosage.Trim();
                row["MarkupPercent"] = item.MarkupPercent.HasValue && item.MarkupPercent.Value > 0m
                    ? item.MarkupPercent.Value
                    : 0m;
                tablePrice.Rows.Add(row);

                current++;
                if (progress != null && (current % 500 == 0 || current == total))
                {
                    progress.Report(new CountProgress(current, total));
                }
            }

            return tablePrice;
        }

        private void SavePriceTable(DataTable tablePrice, IProgress<CountProgress> progress = null)
        {
            const string commandToDelete = "delete from [PriceList]";
            var commandToDeleteTempOrder = string.IsNullOrWhiteSpace(ActiveConsigneeName)
                ? "delete from [TempOrderItems]"
                : "delete from [TempOrderItems] where [ConsigneeName] = @consignee";
            const string commandToInsert = @"INSERT INTO [PriceList] (
[guid_es],
[es_code],
[supplier_id],
[DrugName],
[SupplierName],
[Price],
[Quantity],
[ExpiryPeriod],
[Description],
[Zakaz],
[SummaZakaza],
[supplier_price_id],
[TradeName],
[Dosage],
[MarkupPercent]
)
VALUES
(
@guid_es,
@es_code,
@supplier_id,
@DrugName,
@SupplierName,
@Price,
@Quantity,
@ExpiryPeriod,
@Description,
@Zakaz,
@SummaZakaza,
@supplier_price_id,
@TradeName,
@Dosage,
@MarkupPercent
)";

            int total = tablePrice.Rows.Count;
            using (var sqliteCon = new SQLiteConnection(_connectionStringToLocalDb))
            {
                sqliteCon.Open();

                using (var cmdDeleteBeforeInsert = new SQLiteCommand(commandToDelete, sqliteCon))
                using (var cmdDeleteTempOrder = new SQLiteCommand(commandToDeleteTempOrder, sqliteCon))
                {
                    cmdDeleteBeforeInsert.ExecuteNonQuery();

                    if (!string.IsNullOrWhiteSpace(ActiveConsigneeName) &&
                        commandToDeleteTempOrder.Contains("@consignee"))
                    {
                        cmdDeleteTempOrder.Parameters.AddWithValue("@consignee", ActiveConsigneeName.Trim());
                    }

                    cmdDeleteTempOrder.ExecuteNonQuery();
                }

                using (var transaction = sqliteCon.BeginTransaction())
                using (var cmd = new SQLiteCommand(commandToInsert, sqliteCon, transaction))
                {
                    var pGuidEs = cmd.Parameters.Add("@guid_es", DbType.String);
                    var pEsCode = cmd.Parameters.Add("@es_code", DbType.String);
                    var pSupplierId = cmd.Parameters.Add("@supplier_id", DbType.String);
                    var pDrugName = cmd.Parameters.Add("@DrugName", DbType.String);
                    var pSupplierName = cmd.Parameters.Add("@SupplierName", DbType.String);
                    var pPrice = cmd.Parameters.Add("@Price", DbType.String);
                    var pQuantity = cmd.Parameters.Add("@Quantity", DbType.String);
                    var pExpiry = cmd.Parameters.Add("@ExpiryPeriod", DbType.String);
                    var pDescription = cmd.Parameters.Add("@Description", DbType.String);
                    var pZakaz = cmd.Parameters.Add("@Zakaz", DbType.String);
                    var pSumma = cmd.Parameters.Add("@SummaZakaza", DbType.String);
                    var pSupplierPriceId = cmd.Parameters.Add("@supplier_price_id", DbType.String);
                    var pTradeName = cmd.Parameters.Add("@TradeName", DbType.String);
                    var pDosage = cmd.Parameters.Add("@Dosage", DbType.String);
                    var pMarkup = cmd.Parameters.Add("@MarkupPercent", DbType.Object);

                    int saved = 0;
                    bool hasTradeName = tablePrice.Columns.Contains("TradeName");
                    bool hasDosage = tablePrice.Columns.Contains("Dosage");
                    bool hasMarkup = tablePrice.Columns.Contains("MarkupPercent");

                    foreach (DataRow row in tablePrice.Rows)
                    {
                        pGuidEs.Value = row["guid_es"] ?? DBNull.Value;
                        pEsCode.Value = row["es_code"] ?? DBNull.Value;
                        pSupplierId.Value = row["supplier_id"] ?? DBNull.Value;
                        pDrugName.Value = row["DrugName"] ?? DBNull.Value;
                        pSupplierName.Value = row["SupplierName"] ?? DBNull.Value;
                        pPrice.Value = row["Price"] ?? DBNull.Value;
                        pQuantity.Value = row["Quantity"] ?? DBNull.Value;
                        pExpiry.Value = row["ExpiryPeriod"] ?? DBNull.Value;
                        pDescription.Value = row["Description"] ?? DBNull.Value;
                        pZakaz.Value = row["Zakaz"] ?? DBNull.Value;
                        pSumma.Value = row["SummaZakaza"] ?? DBNull.Value;
                        pSupplierPriceId.Value = row["supplier_price_id"] ?? DBNull.Value;
                        pTradeName.Value = hasTradeName ? (row["TradeName"] ?? DBNull.Value) : DBNull.Value;
                        pDosage.Value = hasDosage ? (row["Dosage"] ?? DBNull.Value) : DBNull.Value;
                        pMarkup.Value = hasMarkup ? (row["MarkupPercent"] ?? DBNull.Value) : DBNull.Value;
                        cmd.ExecuteNonQuery();

                        saved++;
                        if (progress != null && (saved % 250 == 0 || saved == total))
                        {
                            progress.Report(new CountProgress(saved, total));
                        }
                    }

                    transaction.Commit();
                }
            }
        }

        private static int MapPhasePercent(long current, long total, int phaseStart, int phaseEnd)
        {
            if (total <= 0)
            {
                return phaseStart;
            }

            double ratio = Math.Max(0.0, Math.Min(1.0, (double)current / total));
            return phaseStart + (int)((phaseEnd - phaseStart) * ratio);
        }

        private static int PercentOf(long current, long total)
        {
            if (total <= 0)
            {
                return 0;
            }

            return (int)Math.Max(0, Math.Min(100, (100.0 * current) / total));
        }

        private static string FormatBytes(long bytes)
        {
            if (bytes < 1024)
            {
                return $"{bytes} Б";
            }

            if (bytes < 1024 * 1024)
            {
                return $"{bytes / 1024.0:0.0} КБ";
            }

            return $"{bytes / (1024.0 * 1024.0):0.00} МБ";
        }

        private void SetOverallProgress(int percent, string text)
        {
            if (IsDisposed)
            {
                return;
            }

            if (InvokeRequired)
            {
                BeginInvoke(new Action<int, string>(SetOverallProgress), percent, text);
                return;
            }

            if (progressDownload.Style != ProgressBarStyle.Continuous)
            {
                progressDownload.Style = ProgressBarStyle.Continuous;
            }

            progressDownload.Minimum = 0;
            progressDownload.Maximum = 100;
            progressDownload.Value = Math.Max(0, Math.Min(100, percent));
            lblProgress.Text = FormatProgressText(percent, text);
            lblProgress.Refresh();
            progressDownload.Refresh();
        }

        private void ConfigureLogView()
        {
            rtxtDebug.ReadOnly = true;
            rtxtDebug.WordWrap = true;
            rtxtDebug.ScrollBars = RichTextBoxScrollBars.Vertical;
            rtxtDebug.HideSelection = false;
            rtxtDebug.DetectUrls = false;
            rtxtDebug.BorderStyle = BorderStyle.FixedSingle;
            if (rtxtDebug.Font.Name != "Consolas")
            {
                rtxtDebug.Font = new Font("Consolas", 9f);
            }

            lblProgress.AutoEllipsis = false;
            lblProgress.UseMnemonic = false;
        }

        private static string FormatProgressText(int percent, string text)
        {
            return WrapLongText($"{Math.Max(0, Math.Min(100, percent))}% — {text}", 78, 2);
        }

        private static string WrapLongText(string text, int maxLineLength, int maxLines)
        {
            if (string.IsNullOrEmpty(text) || text.Length <= maxLineLength)
            {
                return text ?? string.Empty;
            }

            var lines = new List<string>();
            var remaining = text.Trim();
            while (!string.IsNullOrEmpty(remaining) && lines.Count < maxLines)
            {
                if (remaining.Length <= maxLineLength)
                {
                    lines.Add(remaining);
                    break;
                }

                var breakIndex = remaining.LastIndexOf(' ', Math.Min(maxLineLength, remaining.Length - 1));
                if (breakIndex <= 0)
                {
                    breakIndex = maxLineLength;
                }

                lines.Add(remaining.Substring(0, breakIndex).TrimEnd());
                remaining = remaining.Substring(breakIndex).TrimStart();
            }

            if (!string.IsNullOrEmpty(remaining) && lines.Count >= maxLines)
            {
                var lastLine = lines[lines.Count - 1];
                if (lastLine.Length > maxLineLength - 3)
                {
                    lastLine = lastLine.Substring(0, Math.Max(0, maxLineLength - 3)).TrimEnd();
                }

                lines[lines.Count - 1] = lastLine + "...";
            }

            return string.Join(Environment.NewLine, lines);
        }

        private void AppendStatus(string message)
        {
            if (InvokeRequired)
            {
                BeginInvoke(new Action<string>(AppendStatus), message);
                return;
            }

            var wrapped = WrapLongText(message ?? string.Empty, 96, int.MaxValue);
            rtxtDebug.AppendText(wrapped + Environment.NewLine);
            rtxtDebug.SelectionStart = rtxtDebug.TextLength;
            rtxtDebug.SelectionLength = 0;
            rtxtDebug.ScrollToCaret();
            rtxtDebug.Refresh();
            AppDebugLog.Info("Download", message);
        }

        private void HF_DownloadDataFromServer_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (_isDownloading)
            {
                e.Cancel = true;
            }
        }

        private void btnCloseThis_Click(object sender, EventArgs e)
        {
            Close();
        }

        private sealed class CountProgress
        {
            public CountProgress(int current, int total)
            {
                Current = current;
                Total = total;
            }

            public int Current { get; }
            public int Total { get; }
        }
    }
}


