using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Электронная_Фармация.Classes
{
    public class ApiClient
    {
        private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
        {
            PropertyNamingPolicy = null
        };

        private readonly HttpClient _httpClient;
        private readonly string _baseUrl;
        private string _token;

        public ApiClient(string baseUrl = null)
        {
            _baseUrl = AppConfig.NormalizeApiBaseUrl(baseUrl ?? AppConfig.ApiBaseUrl);
            _httpClient = new HttpClient
            {
                // Крупный прайс тянем одним запросом — даём серверу время посчитать и отдать.
                Timeout = TimeSpan.FromSeconds(120)
            };

            AppDebugLog.Info("ApiClient", $"Создан клиент. BaseUrl={_baseUrl}, Timeout={_httpClient.Timeout.TotalSeconds}s");
        }

        public string BaseUrl => _baseUrl;

        public async Task<string> LoginAsync(string username, string password)
        {
            var request = new LoginRequest
            {
                Username = username,
                Password = password
            };

            var path = "/auth/login";
            var fullUrl = BuildFullUrl(path);
            var requestJson = JsonSerializer.Serialize(request, JsonOptions);
            var stopwatch = Stopwatch.StartNew();

            AppDebugLog.ApiRequest("POST", fullUrl, requestJson, $"username={username}");

            try
            {
                using (var httpRequest = new HttpRequestMessage(HttpMethod.Post, fullUrl))
                {
                    httpRequest.Content = new StringContent(requestJson, Encoding.UTF8, "application/json");
                    var response = await _httpClient.SendAsync(httpRequest);
                    var responseBody = await response.Content.ReadAsStringAsync();
                    stopwatch.Stop();

                    AppDebugLog.ApiResponse("POST", fullUrl, (int)response.StatusCode, stopwatch.ElapsedMilliseconds, responseBody);
                    return ParseLoginResponse(response, responseBody);
                }
            }
            catch (HttpRequestException ex) when (!ex.Message.StartsWith("Ошибка HTTP"))
            {
                stopwatch.Stop();
                AppDebugLog.ApiFailure("POST", fullUrl, stopwatch.ElapsedMilliseconds, ex);
                throw WrapNetworkError(ex, "авторизацию");
            }
            catch (TaskCanceledException ex)
            {
                stopwatch.Stop();
                AppDebugLog.ApiFailure("POST", fullUrl, stopwatch.ElapsedMilliseconds, ex);
                throw WrapNetworkError(ex, "авторизацию");
            }
            catch (Exception ex)
            {
                stopwatch.Stop();
                if (ex is UnauthorizedAccessException || ex is HttpRequestException)
                {
                    AppDebugLog.ApiFailure("POST", fullUrl, stopwatch.ElapsedMilliseconds, ex);
                    throw;
                }

                AppDebugLog.ApiFailure("POST", fullUrl, stopwatch.ElapsedMilliseconds, ex);
                throw new HttpRequestException($"Ошибка при авторизации ({_baseUrl}): {ex.Message}", ex);
            }
        }

        public void SetToken(string token)
        {
            _token = string.IsNullOrWhiteSpace(token) ? null : token;
            AppDebugLog.Info("ApiClient", $"Токен установлен: {AppDebugLog.MaskToken(_token)}");
        }

        public string Token => _token;

        public bool IsAuthenticated => !string.IsNullOrEmpty(_token);

        public async Task<PriceSummaryResponse> GetPriceSummaryAsync(
            string supplierId = null,
            string regionId = null,
            string q = null,
            int? limit = null,
            int? offset = null,
            IProgress<HttpTransferProgress> transferProgress = null)
        {
            EnsureAuthenticated();

            var queryParams = new List<string>();
            if (!string.IsNullOrEmpty(supplierId))
            {
                queryParams.Add($"supplier_id={Uri.EscapeDataString(supplierId)}");
            }
            if (!string.IsNullOrEmpty(regionId))
            {
                queryParams.Add($"region_id={Uri.EscapeDataString(regionId)}");
            }
            if (!string.IsNullOrWhiteSpace(q))
            {
                queryParams.Add($"q={Uri.EscapeDataString(q)}");
            }
            if (limit.HasValue)
            {
                queryParams.Add($"limit={limit.Value}");
            }
            if (offset.HasValue)
            {
                queryParams.Add($"offset={offset.Value}");
            }

            var path = "/api/supplier-prices/summary";
            if (queryParams.Count > 0)
            {
                path += "?" + string.Join("&", queryParams);
            }

            var responseBody = transferProgress == null
                ? await SendAuthorizedGetAsync(path, "загрузку прайса")
                : await SendAuthorizedGetWithProgressAsync(path, "загрузку прайса", transferProgress);

            try
            {
                var summary = JsonSerializer.Deserialize<PriceSummaryResponse>(responseBody, JsonOptions);
                var count = summary?.Summary?.Count ?? 0;
                AppDebugLog.Info("ApiClient", $"Прайс получен: {count} позиций");
                return summary;
            }
            catch (Exception ex)
            {
                AppDebugLog.Error("ApiClient", "Не удалось разобрать ответ прайса", ex);
                throw new HttpRequestException($"Некорректный ответ сервера при загрузке прайса: {ex.Message}", ex);
            }
        }

        public async Task<List<BuyerDto>> GetBuyersAsync()
        {
            EnsureAuthenticated();
            var responseBody = await SendAuthorizedGetAsync("/api/buyers", "загрузку покупателей");
            return DeserializeBuyers(responseBody);
        }

        public async Task<BuyerDto> GetBuyerByIdAsync(string buyerId)
        {
            EnsureAuthenticated();
            if (string.IsNullOrWhiteSpace(buyerId))
            {
                throw new ArgumentException("Не указан buyer_id.", nameof(buyerId));
            }

            var responseBody = await SendAuthorizedGetAsync(
                $"/api/buyers/{Uri.EscapeDataString(buyerId.Trim())}",
                "загрузку покупателя");
            try
            {
                return JsonSerializer.Deserialize<BuyerDto>(responseBody, JsonOptions);
            }
            catch (Exception ex)
            {
                AppDebugLog.Error("ApiClient", "Не удалось разобрать покупателя", ex);
                throw new HttpRequestException($"Некорректный ответ сервера при загрузке покупателя: {ex.Message}", ex);
            }
        }

        public async Task<List<BuyerPriceListAssignmentDto>> GetBuyerPriceListsAsync(string buyerId)
        {
            EnsureAuthenticated();
            if (string.IsNullOrWhiteSpace(buyerId))
            {
                throw new ArgumentException("Не указан buyer_id.", nameof(buyerId));
            }

            var responseBody = await SendAuthorizedGetAsync(
                $"/api/buyers/{Uri.EscapeDataString(buyerId.Trim())}/price-lists",
                "загрузку назначений прайсов покупателя");
            return DeserializeBuyerPriceLists(responseBody);
        }

        public async Task<List<PriceListDto>> GetPriceListsAsync()
        {
            EnsureAuthenticated();
            var responseBody = await SendAuthorizedGetAsync("/api/price-lists", "загрузку прайс-листов");
            return DeserializePriceLists(responseBody);
        }

        public async Task<List<PriceListRegionDto>> GetPriceListRegionsAsync(string priceListId)
        {
            EnsureAuthenticated();
            if (string.IsNullOrWhiteSpace(priceListId))
            {
                throw new ArgumentException("Не указан price_list_id.", nameof(priceListId));
            }

            var path = "/api/sc/price-list-regions?price_list_id=" + Uri.EscapeDataString(priceListId.Trim());
            var responseBody = await SendAuthorizedGetAsync(path, "загрузку региональных наценок прайса");
            return DeserializePriceListRegions(responseBody);
        }

        private static List<BuyerDto> DeserializeBuyers(string responseBody)
        {
            try
            {
                using (var doc = JsonDocument.Parse(responseBody))
                {
                    if (doc.RootElement.ValueKind == JsonValueKind.Array)
                    {
                        return JsonSerializer.Deserialize<List<BuyerDto>>(responseBody, JsonOptions)
                               ?? new List<BuyerDto>();
                    }

                    var wrapped = JsonSerializer.Deserialize<BuyersResponse>(responseBody, JsonOptions);
                    return wrapped?.Buyers ?? wrapped?.Items ?? new List<BuyerDto>();
                }
            }
            catch (Exception ex)
            {
                AppDebugLog.Error("ApiClient", "Не удалось разобрать список покупателей", ex);
                throw new HttpRequestException($"Некорректный ответ сервера при загрузке покупателей: {ex.Message}", ex);
            }
        }

        private static List<BuyerPriceListAssignmentDto> DeserializeBuyerPriceLists(string responseBody)
        {
            try
            {
                using (var doc = JsonDocument.Parse(responseBody))
                {
                    if (doc.RootElement.ValueKind == JsonValueKind.Array)
                    {
                        return JsonSerializer.Deserialize<List<BuyerPriceListAssignmentDto>>(responseBody, JsonOptions)
                               ?? new List<BuyerPriceListAssignmentDto>();
                    }

                    var wrapped = JsonSerializer.Deserialize<BuyerPriceListsResponse>(responseBody, JsonOptions);
                    return wrapped?.Items ?? wrapped?.PriceLists ?? new List<BuyerPriceListAssignmentDto>();
                }
            }
            catch (Exception ex)
            {
                AppDebugLog.Error("ApiClient", "Не удалось разобрать назначения прайсов покупателя", ex);
                throw new HttpRequestException(
                    $"Некорректный ответ сервера при загрузке назначений прайсов: {ex.Message}",
                    ex);
            }
        }

        private static List<PriceListDto> DeserializePriceLists(string responseBody)
        {
            try
            {
                using (var doc = JsonDocument.Parse(responseBody))
                {
                    if (doc.RootElement.ValueKind == JsonValueKind.Array)
                    {
                        return JsonSerializer.Deserialize<List<PriceListDto>>(responseBody, JsonOptions)
                               ?? new List<PriceListDto>();
                    }

                    var wrapped = JsonSerializer.Deserialize<PriceListsResponse>(responseBody, JsonOptions);
                    return wrapped?.PriceLists ?? wrapped?.Items ?? new List<PriceListDto>();
                }
            }
            catch (Exception ex)
            {
                AppDebugLog.Error("ApiClient", "Не удалось разобрать прайс-листы", ex);
                throw new HttpRequestException($"Некорректный ответ сервера при загрузке прайс-листов: {ex.Message}", ex);
            }
        }

        private static List<PriceListRegionDto> DeserializePriceListRegions(string responseBody)
        {
            try
            {
                using (var doc = JsonDocument.Parse(responseBody))
                {
                    if (doc.RootElement.ValueKind == JsonValueKind.Array)
                    {
                        return JsonSerializer.Deserialize<List<PriceListRegionDto>>(responseBody, JsonOptions)
                               ?? new List<PriceListRegionDto>();
                    }
                }

                return new List<PriceListRegionDto>();
            }
            catch (Exception ex)
            {
                AppDebugLog.Error("ApiClient", "Не удалось разобрать региональные наценки прайса", ex);
                throw new HttpRequestException(
                    $"Некорректный ответ сервера при загрузке региональных наценок: {ex.Message}",
                    ex);
            }
        }

        public async Task<List<InvoiceApiItem>> GetInvoicesAsync()
        {
            var orders = await GetBuyerOrdersAsync(status: "Placed", limit: 200);
            var result = new List<InvoiceApiItem>();

            foreach (var order in orders)
            {
                var detail = await GetBuyerOrderByIdAsync(order.OrderID);
                if (detail == null)
                {
                    continue;
                }

                result.Add(InvoiceApiItem.FromBuyerOrder(detail));
            }

            AppDebugLog.Info("ApiClient", $"Накладные (через buyer/orders) получены: {result.Count} шт.");
            return result;
        }

        public async Task<List<BuyerOrderListItemDto>> GetBuyerOrdersAsync(string status = null, int limit = 200, int offset = 0)
        {
            EnsureAuthenticated();

            var query = new List<string>();
            if (!string.IsNullOrWhiteSpace(status))
            {
                query.Add($"status={Uri.EscapeDataString(status)}");
            }

            if (limit > 0)
            {
                query.Add($"limit={limit}");
            }

            if (offset > 0)
            {
                query.Add($"offset={offset}");
            }

            var path = "/api/buyer/orders";
            if (query.Count > 0)
            {
                path += "?" + string.Join("&", query);
            }

            var responseBody = await SendAuthorizedGetAsync(path, "загрузку заказов");
            try
            {
                var response = JsonSerializer.Deserialize<BuyerOrdersResponse>(responseBody, JsonOptions);
                return response?.Orders ?? new List<BuyerOrderListItemDto>();
            }
            catch (Exception ex)
            {
                AppDebugLog.Error("ApiClient", "Не удалось разобрать список заказов", ex);
                throw new HttpRequestException($"Некорректный ответ сервера при загрузке заказов: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// Отчёт покупателя: позиции его заказов за период. Сервер скоупит по JWT покупателя.
        /// </summary>
        public async Task<List<BuyerReportLineDto>> GetBuyerReportAsync(DateTime from, DateTime to)
        {
            EnsureAuthenticated();
            var path = $"/api/buyer/report?date_from={from:yyyy-MM-dd}&date_to={to:yyyy-MM-dd}";
            var responseBody = await SendAuthorizedGetAsync(path, "формирование отчёта");
            try
            {
                var response = JsonSerializer.Deserialize<BuyerReportResponse>(responseBody, JsonOptions);
                return response?.Lines ?? new List<BuyerReportLineDto>();
            }
            catch (Exception ex)
            {
                AppDebugLog.Error("ApiClient", "Не удалось разобрать отчёт", ex);
                throw new HttpRequestException($"Некорректный ответ сервера при формировании отчёта: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// Отправляет свободный запрос («промт») ИИ-сервису и получает готовый
        /// документ (Word/Excel). Токен покупателя уходит в теле — сервис ходит на
        /// платформу от его имени и видит ТОЛЬКО его данные.
        /// </summary>
        public async Task<AiReportResult> GenerateAiReportAsync(string prompt, string format = "docx")
        {
            EnsureAuthenticated();
            if (string.IsNullOrWhiteSpace(prompt))
            {
                throw new ArgumentException("Пустой запрос для ИИ.", nameof(prompt));
            }

            var aiBase = AppConfig.AiBaseUrl.TrimEnd('/');
            var fullUrl = new Uri(new Uri(aiBase + "/"), "generate").ToString();
            var payload = new AiReportRequest { Prompt = prompt, Token = _token, Format = format };
            var requestJson = JsonSerializer.Serialize(payload, JsonOptions);
            var stopwatch = Stopwatch.StartNew();

            AppDebugLog.ApiRequest("POST", fullUrl, extra: $"ai prompt len={prompt.Length}, format={format}");

            try
            {
                using (var httpRequest = new HttpRequestMessage(HttpMethod.Post, fullUrl))
                {
                    httpRequest.Content = new StringContent(requestJson, Encoding.UTF8, "application/json");
                    var response = await _httpClient.SendAsync(httpRequest);
                    stopwatch.Stop();

                    if (!response.IsSuccessStatusCode)
                    {
                        var errBody = await response.Content.ReadAsStringAsync();
                        AppDebugLog.ApiHttpError("POST", fullUrl, (int)response.StatusCode, errBody, "ai-report");
                        var detail = TryDeserializeError(errBody)?.Error ?? errBody;
                        throw new HttpRequestException($"ИИ-сервис вернул {(int)response.StatusCode}: {detail}");
                    }

                    var bytes = await response.Content.ReadAsByteArrayAsync();
                    var fileName = response.Content.Headers.ContentDisposition?.FileNameStar
                                   ?? response.Content.Headers.ContentDisposition?.FileName
                                   ?? (format == "xlsx" ? "Отчёт.xlsx" : "Отчёт.docx");
                    fileName = fileName.Trim('"');
                    AppDebugLog.ApiResponse("POST", fullUrl, (int)response.StatusCode, stopwatch.ElapsedMilliseconds,
                        $"ai file={fileName}, {bytes.Length} bytes");
                    return new AiReportResult { Content = bytes, FileName = fileName };
                }
            }
            catch (HttpRequestException)
            {
                throw;
            }
            catch (Exception ex)
            {
                stopwatch.Stop();
                throw WrapNetworkError(ex, "формирование ИИ-отчёта");
            }
        }

        public async Task<List<BuyerLocationDto>> GetBuyerLocationsAsync()
        {
            EnsureAuthenticated();
            var responseBody = await SendAuthorizedGetAsync("/api/buyer/locations", "загрузку грузополучателей");
            try
            {
                using (var doc = JsonDocument.Parse(responseBody))
                {
                    if (doc.RootElement.ValueKind == JsonValueKind.Array)
                    {
                        return JsonSerializer.Deserialize<List<BuyerLocationDto>>(responseBody, JsonOptions)
                               ?? new List<BuyerLocationDto>();
                    }
                }

                return new List<BuyerLocationDto>();
            }
            catch (Exception ex)
            {
                AppDebugLog.Error("ApiClient", "Не удалось разобрать грузополучателей", ex);
                throw new HttpRequestException($"Некорректный ответ сервера при загрузке грузополучателей: {ex.Message}", ex);
            }
        }

        public async Task<BuyerOrderDetailDto> GetBuyerOrderByIdAsync(string orderId)
        {
            EnsureAuthenticated();
            if (string.IsNullOrWhiteSpace(orderId))
            {
                throw new ArgumentException("Не указан ID заказа.", nameof(orderId));
            }

            var responseBody = await SendAuthorizedGetAsync($"/api/buyer/orders/{Uri.EscapeDataString(orderId)}", "загрузку заказа");
            try
            {
                return JsonSerializer.Deserialize<BuyerOrderDetailDto>(responseBody, JsonOptions);
            }
            catch (Exception ex)
            {
                AppDebugLog.Error("ApiClient", $"Не удалось разобрать заказ {orderId}", ex);
                throw new HttpRequestException($"Некорректный ответ сервера при загрузке заказа: {ex.Message}", ex);
            }
        }

        public async Task<BuyerOrderDetailDto> PlaceBuyerOrderAsync(string orderId)
        {
            EnsureAuthenticated();
            if (string.IsNullOrWhiteSpace(orderId))
            {
                throw new ArgumentException("Не указан ID заказа.", nameof(orderId));
            }

            var responseBody = await SendAuthorizedPostAsync($"/api/buyer/orders/{Uri.EscapeDataString(orderId)}/place", string.Empty, "оформление заказа");
            try
            {
                return JsonSerializer.Deserialize<BuyerOrderDetailDto>(responseBody, JsonOptions);
            }
            catch (Exception ex)
            {
                AppDebugLog.Error("ApiClient", $"Не удалось разобрать ответ оформления заказа {orderId}", ex);
                throw new HttpRequestException($"Некорректный ответ сервера при оформлении заказа: {ex.Message}", ex);
            }
        }

        private void EnsureAuthenticated()
        {
            if (!IsAuthenticated)
            {
                var ex = new InvalidOperationException("Необходимо выполнить авторизацию перед запросом данных");
                AppDebugLog.Error("ApiClient", "Запрос без токена авторизации", ex);
                throw ex;
            }
        }

        private async Task<string> SendAuthorizedGetAsync(string path, string operationName)
        {
            return await SendAuthorizedGetWithProgressAsync(path, operationName, null);
        }

        private async Task<string> SendAuthorizedPostAsync(string path, string requestBody, string operationName)
        {
            var fullUrl = BuildFullUrl(path);
            var stopwatch = Stopwatch.StartNew();

            AppDebugLog.ApiRequest("POST", fullUrl, requestBody, $"token={AppDebugLog.MaskToken(_token)}");

            try
            {
                using (var httpRequest = new HttpRequestMessage(HttpMethod.Post, fullUrl))
                {
                    httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);
                    httpRequest.Content = new StringContent(requestBody ?? string.Empty, Encoding.UTF8, "application/json");
                    var response = await _httpClient.SendAsync(httpRequest);
                    var responseBody = await response.Content.ReadAsStringAsync();
                    stopwatch.Stop();

                    AppDebugLog.ApiResponse("POST", fullUrl, (int)response.StatusCode, stopwatch.ElapsedMilliseconds, responseBody);

                    if (response.IsSuccessStatusCode)
                    {
                        return responseBody;
                    }

                    if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
                    {
                        _token = null;
                        var error = TryDeserializeError(responseBody);
                        var message = $"Сессия истекла: {error?.Error ?? "Необходимо войти заново"}";
                        AppDebugLog.ApiHttpError("POST", fullUrl, (int)response.StatusCode, responseBody, operationName);
                        throw new UnauthorizedAccessException(message);
                    }

                    var httpError = TryDeserializeError(responseBody);
                    var httpMessage = $"Ошибка HTTP {response.StatusCode}: {httpError?.Error ?? responseBody}";
                    AppDebugLog.ApiHttpError("POST", fullUrl, (int)response.StatusCode, responseBody, operationName);
                    throw new HttpRequestException(httpMessage);
                }
            }
            catch (UnauthorizedAccessException)
            {
                throw;
            }
            catch (HttpRequestException ex) when (!ex.Message.StartsWith("Ошибка HTTP"))
            {
                stopwatch.Stop();
                AppDebugLog.ApiFailure("POST", fullUrl, stopwatch.ElapsedMilliseconds, ex);
                throw WrapNetworkError(ex, operationName);
            }
            catch (TaskCanceledException ex)
            {
                stopwatch.Stop();
                AppDebugLog.ApiFailure("POST", fullUrl, stopwatch.ElapsedMilliseconds, ex);
                throw WrapNetworkError(ex, operationName);
            }
        }

        private async Task<string> SendAuthorizedGetWithProgressAsync(
            string path,
            string operationName,
            IProgress<HttpTransferProgress> transferProgress)
        {
            var fullUrl = BuildFullUrl(path);
            var stopwatch = Stopwatch.StartNew();

            AppDebugLog.ApiRequest("GET", fullUrl, extra: $"token={AppDebugLog.MaskToken(_token)}");

            try
            {
                using (var httpRequest = new HttpRequestMessage(HttpMethod.Get, fullUrl))
                {
                    httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);
                    var response = await _httpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead);
                    var responseBody = await ReadContentWithProgressAsync(response, transferProgress);
                    stopwatch.Stop();

                    AppDebugLog.ApiResponse("GET", fullUrl, (int)response.StatusCode, stopwatch.ElapsedMilliseconds, responseBody);

                    if (response.IsSuccessStatusCode)
                    {
                        return responseBody;
                    }

                    if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
                    {
                        _token = null;
                        var error = TryDeserializeError(responseBody);
                        var message = $"Сессия истекла: {error?.Error ?? "Необходимо войти заново"}";
                        AppDebugLog.ApiHttpError("GET", fullUrl, (int)response.StatusCode, responseBody, operationName);
                        var unauthorized = new UnauthorizedAccessException(message);
                        AppDebugLog.Error("ApiClient", message, unauthorized);
                        throw unauthorized;
                    }

                    var httpError = TryDeserializeError(responseBody);
                    var httpMessage = $"Ошибка HTTP {response.StatusCode}: {httpError?.Error ?? responseBody}";
                    AppDebugLog.ApiHttpError("GET", fullUrl, (int)response.StatusCode, responseBody, operationName);
                    var httpEx = new HttpRequestException(httpMessage);
                    AppDebugLog.Error("ApiClient", httpMessage, httpEx);
                    throw httpEx;
                }
            }
            catch (HttpRequestException ex) when (!ex.Message.StartsWith("Ошибка HTTP"))
            {
                stopwatch.Stop();
                AppDebugLog.ApiFailure("GET", fullUrl, stopwatch.ElapsedMilliseconds, ex);
                throw WrapNetworkError(ex, operationName);
            }
            catch (TaskCanceledException ex)
            {
                stopwatch.Stop();
                AppDebugLog.ApiFailure("GET", fullUrl, stopwatch.ElapsedMilliseconds, ex);
                throw WrapNetworkError(ex, operationName);
            }
            catch (UnauthorizedAccessException)
            {
                throw;
            }
            catch (HttpRequestException)
            {
                throw;
            }
            catch (Exception ex)
            {
                stopwatch.Stop();
                AppDebugLog.ApiFailure("GET", fullUrl, stopwatch.ElapsedMilliseconds, ex);
                throw;
            }
        }

        private static async Task<string> ReadContentWithProgressAsync(
            HttpResponseMessage response,
            IProgress<HttpTransferProgress> transferProgress)
        {
            var totalBytes = response.Content.Headers.ContentLength;
            using (var stream = await response.Content.ReadAsStreamAsync())
            using (var memory = new System.IO.MemoryStream())
            {
                var buffer = new byte[81920];
                long received = 0;
                int read;
                while ((read = await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0)
                {
                    memory.Write(buffer, 0, read);
                    received += read;
                    transferProgress?.Report(new HttpTransferProgress(received, totalBytes));
                }

                transferProgress?.Report(new HttpTransferProgress(received, totalBytes ?? received));
                return Encoding.UTF8.GetString(memory.ToArray());
            }
        }

        private string ParseLoginResponse(HttpResponseMessage response, string responseBody)
        {
            if (response.IsSuccessStatusCode)
            {
                try
                {
                    var loginResponse = JsonSerializer.Deserialize<LoginResponse>(responseBody, JsonOptions);
                    if (loginResponse == null || string.IsNullOrWhiteSpace(loginResponse.Token))
                    {
                        var emptyTokenEx = new HttpRequestException($"Сервер {_baseUrl} вернул пустой токен авторизации");
                        AppDebugLog.Error("ApiClient", "Пустой токен в ответе логина", emptyTokenEx);
                        throw emptyTokenEx;
                    }

                    _token = loginResponse.Token;
                    AppDebugLog.Info("ApiClient", $"Авторизация успешна. token={AppDebugLog.MaskToken(_token)}");
                    return _token;
                }
                catch (HttpRequestException)
                {
                    throw;
                }
                catch (Exception ex)
                {
                    AppDebugLog.Error("ApiClient", "Не удалось разобрать ответ логина", ex);
                    throw new HttpRequestException($"Некорректный ответ сервера при авторизации: {ex.Message}", ex);
                }
            }

            if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
            {
                var error = TryDeserializeError(responseBody);
                var message = $"Ошибка авторизации: {error?.Error ?? "Неверные учетные данные"}";
                AppDebugLog.ApiHttpError("POST", BuildFullUrl("/auth/login"), (int)response.StatusCode, responseBody, "login");
                var unauthorized = new UnauthorizedAccessException(message);
                AppDebugLog.Error("ApiClient", message, unauthorized);
                throw unauthorized;
            }

            var httpError = TryDeserializeError(responseBody);
            var httpMessage = $"Ошибка HTTP {response.StatusCode}: {httpError?.Error ?? responseBody}";
            AppDebugLog.ApiHttpError("POST", BuildFullUrl("/auth/login"), (int)response.StatusCode, responseBody, "login");
            var httpEx = new HttpRequestException(httpMessage);
            AppDebugLog.Error("ApiClient", httpMessage, httpEx);
            throw httpEx;
        }

        private string BuildFullUrl(string path)
        {
            return new Uri(new Uri(_baseUrl + "/"), path.TrimStart('/')).ToString();
        }

        private static ErrorResponse TryDeserializeError(string body)
        {
            if (string.IsNullOrWhiteSpace(body))
            {
                return null;
            }

            try
            {
                return JsonSerializer.Deserialize<ErrorResponse>(body, JsonOptions);
            }
            catch
            {
                return null;
            }
        }

        private HttpRequestException WrapNetworkError(Exception ex, string operation)
        {
            var details = ex.InnerException != null && ex.InnerException.Message != ex.Message
                ? $" ({ex.InnerException.Message})"
                : string.Empty;

            return new HttpRequestException(
                $"Не удалось выполнить {operation}. Сервер: {_baseUrl}{details}. {ex.Message}",
                ex);
        }
    }

    public sealed class InvoiceApiItem
    {
        public string InvoiceNumber { get; set; }
        public string InvoiceDate { get; set; }
        public string SupplierName { get; set; }
        public string ConsigneeName { get; set; }
        public string InvoiceSum { get; set; }
        public string RefuseSum { get; set; }

        public string SourceOrderId { get; set; }
        public List<InvoiceApiOrderItem> Items { get; set; } = new List<InvoiceApiOrderItem>();

        public static InvoiceApiItem FromBuyerOrder(BuyerOrderDetailDto detail)
        {
            var supplierNames = detail.Items == null
                ? string.Empty
                : string.Join(", ", detail.Items
                    .Select(i => i.Supplier)
                    .Where(s => !string.IsNullOrWhiteSpace(s))
                    .Distinct());

            var invoiceDate = detail.PlacedAt ?? detail.CreatedAt;
            var invoice = new InvoiceApiItem
            {
                InvoiceNumber = string.IsNullOrWhiteSpace(detail.LocalOrderNumber)
                    ? detail.OrderID
                    : detail.LocalOrderNumber,
                InvoiceDate = invoiceDate?.ToString("yyyy-MM-dd") ?? string.Empty,
                SupplierName = supplierNames,
                ConsigneeName = detail.LocationAddress ?? string.Empty,
                InvoiceSum = (detail.TotalAmount ?? 0m).ToString(System.Globalization.CultureInfo.InvariantCulture),
                RefuseSum = "0",
                SourceOrderId = detail.OrderID
            };

            if (detail.Items != null)
            {
                foreach (var item in detail.Items)
                {
                    invoice.Items.Add(new InvoiceApiOrderItem
                    {
                        GoodCode = item.ItemCode ?? string.Empty,
                        GoodName = item.Name ?? string.Empty,
                        SupplierName = item.Supplier ?? string.Empty,
                        Qty = item.Qty,
                        Price = item.UnitPrice,
                        Total = item.Total
                    });
                }
            }

            return invoice;
        }
    }

    public sealed class InvoiceApiOrderItem
    {
        public string GoodCode { get; set; }
        public string GoodName { get; set; }
        public string SupplierName { get; set; }
        public decimal Qty { get; set; }
        public decimal Price { get; set; }
        public decimal Total { get; set; }
    }

    public sealed class BuyerOrdersResponse
    {
        [JsonPropertyName("orders")]
        public List<BuyerOrderListItemDto> Orders { get; set; }
    }

    public sealed class AiReportRequest
    {
        [JsonPropertyName("prompt")]
        public string Prompt { get; set; }

        [JsonPropertyName("token")]
        public string Token { get; set; }

        [JsonPropertyName("format")]
        public string Format { get; set; }
    }

    public sealed class AiReportResult
    {
        public byte[] Content { get; set; }
        public string FileName { get; set; }
    }

    public sealed class BuyerReportResponse
    {
        [JsonPropertyName("lines")]
        public List<BuyerReportLineDto> Lines { get; set; }
    }

    public sealed class BuyerReportLineDto
    {
        [JsonPropertyName("order_id")]
        public string OrderID { get; set; }

        [JsonPropertyName("global_sign")]
        public string GlobalSign { get; set; }

        [JsonPropertyName("order_date")]
        public DateTime? OrderDate { get; set; }

        [JsonPropertyName("status")]
        public string Status { get; set; }

        [JsonPropertyName("supplier")]
        public string Supplier { get; set; }

        [JsonPropertyName("location")]
        public string Location { get; set; }

        [JsonPropertyName("item_name")]
        public string ItemName { get; set; }

        [JsonPropertyName("item_code")]
        public string ItemCode { get; set; }

        [JsonPropertyName("qty")]
        public decimal Qty { get; set; }

        [JsonPropertyName("unit_price")]
        public decimal UnitPrice { get; set; }

        [JsonPropertyName("sum")]
        public decimal Sum { get; set; }
    }

    public sealed class BuyerLocationDto
    {
        [JsonPropertyName("buyer_location_id")]
        public string BuyerLocationID { get; set; }

        [JsonPropertyName("buyer_id")]
        public string BuyerID { get; set; }

        [JsonPropertyName("address")]
        public string Address { get; set; }

        [JsonPropertyName("region_id")]
        public string RegionID { get; set; }

        [JsonPropertyName("is_default")]
        public bool IsDefault { get; set; }

        [JsonPropertyName("region_name")]
        public string RegionName { get; set; }
    }

    public sealed class BuyerOrderListItemDto
    {
        [JsonPropertyName("order_id")]
        public string OrderID { get; set; }

        [JsonPropertyName("status")]
        public string Status { get; set; }
    }

    public sealed class BuyerOrderDetailDto
    {
        [JsonPropertyName("order_id")]
        public string OrderID { get; set; }

        [JsonPropertyName("status")]
        public string Status { get; set; }

        [JsonPropertyName("total_amount")]
        public decimal? TotalAmount { get; set; }

        [JsonPropertyName("created_at")]
        public DateTime? CreatedAt { get; set; }

        [JsonPropertyName("placed_at")]
        public DateTime? PlacedAt { get; set; }

        [JsonPropertyName("comment")]
        public string Comment { get; set; }

        [JsonPropertyName("location_address")]
        public string LocationAddress { get; set; }

        [JsonPropertyName("items")]
        public List<BuyerOrderItemDetailDto> Items { get; set; }

        [JsonIgnore]
        public string LocalOrderNumber { get; set; }
    }

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

        [JsonPropertyName("supplier")]
        public string Supplier { get; set; }

        [JsonPropertyName("qty")]
        public decimal Qty { get; set; }

        [JsonPropertyName("unit_price")]
        public decimal UnitPrice { get; set; }

        [JsonPropertyName("total")]
        public decimal Total { get; set; }

        [JsonPropertyName("item_code")]
        public string ItemCode { get; set; }
    }

    public sealed class HttpTransferProgress
    {
        public HttpTransferProgress(long bytesReceived, long? totalBytes)
        {
            BytesReceived = bytesReceived;
            TotalBytes = totalBytes;
        }

        public long BytesReceived { get; }
        public long? TotalBytes { get; }

        public int Percent
        {
            get
            {
                if (!TotalBytes.HasValue || TotalBytes.Value <= 0)
                {
                    return 0;
                }

                return (int)Math.Max(0, Math.Min(100, (100.0 * BytesReceived) / TotalBytes.Value));
            }
        }
    }
}
