using Mango.Nop.Core.Loggers;
using Nop.Services.Configuration;
using System.Text;
using System.Text.Json;

#nullable enable
namespace Nop.Plugin.Misc.FruitBankPlugin.Services
{
    /// <summary>
    /// Z.ai GLM-OCR service — szállítólevelek, rendelési dokumentumok strukturált szövegkinyerésére.
    /// Endpoint: POST https://api.z.ai/api/paas/v4/layout_parsing
    /// Konfiguráció: FruitBankSettings.ZaiApiKey (+ opcionális ZaiModel, default: "glm-ocr")
    ///
    /// Output formátum: Markdown + HTML vegyes szöveg (md_results mező).
    /// A táblázatokat &lt;table&gt;/&lt;thead&gt;/&lt;td&gt; tagekben adja vissza — LLM-nek közvetlenül átadható.
    /// </summary>
    public class ZaiService
    {
        private const string LayoutParsingEndpoint = "https://api.z.ai/api/paas/v4/layout_parsing";
        private const string DefaultModel = "glm-ocr";

        private readonly ISettingService _settingService;
        private readonly FruitBankSettings _settings;
        private readonly HttpClient _httpClient;
        private readonly ILogger<ZaiService> _logger;

        public ZaiService(
            ISettingService settingService,
            HttpClient httpClient,
            ILogger<ZaiService> logger)
        {
            _settingService = settingService;
            _settings = _settingService.LoadSetting<FruitBankSettings>();
            _httpClient = httpClient;
            _logger = logger;
        }

        private string ApiKey => _settings.ZaiApiKey
            ?? throw new InvalidOperationException("ZAI API kulcs nincs konfigurálva (FruitBankSettings.ZaiApiKey).");

        private string Model => string.IsNullOrWhiteSpace(_settings.ZaiModel)
            ? DefaultModel
            : _settings.ZaiModel;

        // ── Publikus API ─────────────────────────────────────────────────────────────

        /// <summary>
        /// OCR elemzés nyilvánosan elérhető URL alapján (kép vagy PDF).
        /// </summary>
        /// <param name="fileUrl">Nyilvánosan elérhető HTTP(S) URL. Kép: max 10 MB, PDF: max 50 MB / 100 oldal.</param>
        public async Task<ZaiOcrResult> AnalyzeUrlAsync(string fileUrl)
        {
            if (string.IsNullOrWhiteSpace(fileUrl))
                return ZaiOcrResult.Failure("A fileUrl paraméter üres.");

            var body = JsonSerializer.Serialize(new { model = Model, file = fileUrl });
            return await CallApiAsync(body);
        }

        /// <summary>
        /// OCR elemzés memóriából (Stream).
        /// A stream tartalma base64-re konvertálódik, majd data URI-ként kerül az API-hoz.
        /// </summary>
        /// <param name="stream">Kép vagy PDF stream.</param>
        /// <param name="mimeType">MIME típus, pl. "image/jpeg", "application/pdf".</param>
        public async Task<ZaiOcrResult> AnalyzeStreamAsync(Stream stream, string mimeType)
        {
            if (stream == null || stream.Length == 0)
                return ZaiOcrResult.Failure("Az átadott stream üres.");

            byte[] bytes;
            using (var ms = new MemoryStream())
            {
                await stream.CopyToAsync(ms);
                bytes = ms.ToArray();
            }

            return await AnalyzeBase64Async(Convert.ToBase64String(bytes), mimeType);
        }

        /// <summary>
        /// OCR elemzés base64 kódolt adat alapján.
        /// Ha az adat még nem tartalmazza a "data:" prefixet, automatikusan data URI-vá alakítja.
        /// </summary>
        /// <param name="base64Data">Nyers base64 vagy teljes data URI.</param>
        /// <param name="mimeType">MIME típus (csak nyers base64 esetén szükséges).</param>
        public async Task<ZaiOcrResult> AnalyzeBase64Async(string base64Data, string mimeType = "image/jpeg")
        {
            if (string.IsNullOrWhiteSpace(base64Data))
                return ZaiOcrResult.Failure("A base64Data paraméter üres.");

            var dataUri = base64Data.StartsWith("data:", StringComparison.OrdinalIgnoreCase)
                ? base64Data
                : $"data:{mimeType};base64,{base64Data}";

            var body = JsonSerializer.Serialize(new { model = Model, file = dataUri });
            return await CallApiAsync(body);
        }

        // ── Segédmetódus: MIME típus meghatározása fájlnév alapján ──────────────────

        /// <summary>
        /// Fájlkiterjesztés alapján visszaadja a megfelelő MIME típust.
        /// </summary>
        public static string GetMimeType(string fileName)
        {
            var ext = Path.GetExtension(fileName).ToLowerInvariant();
            return ext switch
            {
                ".pdf"          => "application/pdf",
                ".jpg" or ".jpeg" => "image/jpeg",
                ".png"          => "image/png",
                ".gif"          => "image/gif",
                ".webp"         => "image/webp",
                ".tif" or ".tiff" => "image/tiff",
                _               => "application/octet-stream"
            };
        }

        // ── Belső API hívás ──────────────────────────────────────────────────────────

        private async Task<ZaiOcrResult> CallApiAsync(string jsonBody)
        {
            using var request = new HttpRequestMessage(HttpMethod.Post, LayoutParsingEndpoint);
            request.Headers.Add("Authorization", $"Bearer {ApiKey}");
            request.Content = new StringContent(jsonBody, Encoding.UTF8, "application/json");

            try
            {
                var response = await _httpClient.SendAsync(request);
                var responseBody = await response.Content.ReadAsStringAsync();

                if (!response.IsSuccessStatusCode)
                {
                    _logger.Error("ZAI API hiba {StatusCode}: {Body}", new Exception(responseBody));
                    return ZaiOcrResult.Failure($"API hiba {(int)response.StatusCode}: {responseBody}");
                }

                using var doc = JsonDocument.Parse(responseBody);
                var root = doc.RootElement;

                var markdown = TryGetMarkdown(root);

                // Token statisztika logolása (debug szinten, hogy ne spammelje a logot)
                if (root.TryGetProperty("usage", out var usage))
                {
                    var prompt = usage.TryGetProperty("prompt_tokens", out var pt) ? pt.GetInt32() : 0;
                    var completion = usage.TryGetProperty("completion_tokens", out var ct) ? ct.GetInt32() : 0;
                    _logger.Debug("ZAI GLM-OCR token felhasználás: {Prompt} + {Completion} = {Total}" +$"{prompt}, {completion}, {prompt} + {completion}");
                }

                if (string.IsNullOrEmpty(markdown))
                {
                    _logger.Warning("ZAI GLM-OCR: md_results mező üres. Raw válasz: {Body}", responseBody);
                    return ZaiOcrResult.Failure("Az OCR eredmény üres (md_results mező hiányzik a válaszból).");
                }

                _logger.Debug($"ZAI GLM-OCR sikeres, karakter { markdown.Length}");
                return ZaiOcrResult.Success(markdown, responseBody);
            }
            catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
            {
                _logger.Error("ZAI GLM-OCR időtúllépés", ex);
                return ZaiOcrResult.Failure("Időtúllépés: a GLM-OCR API nem válaszolt időben. Nagy PDF-eknél növeld a HttpClient timeout-ját.");
            }
            catch (Exception ex)
            {
                _logger.Error("ZAI GLM-OCR hívás kivétellel végződött", ex);
                return ZaiOcrResult.Failure($"Hálózati hiba: {ex.Message}");
            }
        }

        /// <summary>
        /// Az md_results mezőt keresi elsőként (layout_parsing API), majd fallback-eket próbál
        /// a chat completion API formátumhoz — így a service toleráns az esetleges API verziókkal szemben.
        /// </summary>
        private static string TryGetMarkdown(JsonElement root)
        {
            // Elsődleges: layout_parsing endpoint saját mezője
            if (root.TryGetProperty("md_results", out var mdResults))
            {
                var val = mdResults.GetString();
                if (!string.IsNullOrEmpty(val)) return val;
            }

            // Fallback 1: chat completion stílusú choices tömb
            if (root.TryGetProperty("choices", out var choices) &&
                choices.GetArrayLength() > 0 &&
                choices[0].TryGetProperty("message", out var msg) &&
                msg.TryGetProperty("content", out var content))
            {
                var val = content.GetString();
                if (!string.IsNullOrEmpty(val)) return val;
            }

            // Fallback 2: egyszerű result mező
            if (root.TryGetProperty("result", out var result))
            {
                var val = result.GetString();
                if (!string.IsNullOrEmpty(val)) return val;
            }

            return string.Empty;
        }
    }

    // ── Result record ────────────────────────────────────────────────────────────────

    /// <summary>
    /// A ZaiService által visszaadott OCR eredmény.
    /// </summary>
    public sealed class ZaiOcrResult
    {
        public bool IsSuccess { get; private init; }

        /// <summary>
        /// A teljes dokumentum Markdown+HTML vegyes formátumban.
        /// Táblázatokat &lt;table&gt;/&lt;th&gt;/&lt;td&gt; tagek tartalmazzák — LLM promptba közvetlenül illeszthető.
        /// </summary>
        public string Markdown { get; private init; } = string.Empty;

        /// <summary>
        /// A nyers JSON válasz (diagnosztikához / layout_details feldolgozáshoz).
        /// </summary>
        public string? RawResponse { get; private init; }

        public string? ErrorMessage { get; private init; }

        public static ZaiOcrResult Success(string markdown, string raw) =>
            new() { IsSuccess = true, Markdown = markdown, RawResponse = raw };

        public static ZaiOcrResult Failure(string error) =>
            new() { IsSuccess = false, ErrorMessage = error };
    }
}
