using System.Text.Json;
using Health.Application.AI;
using Microsoft.Extensions.Logging;
using UglyToad.PdfPig;

namespace Health.Infrastructure.AI;

public sealed class AttachmentContextBuilder(
    VisionClient vision,
    ILogger<AttachmentContextBuilder> logger) : IAttachmentContextBuilder
{
    private const int MaxPdfChars = 6000;
    private static readonly JsonSerializerOptions JsonOpts = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        PropertyNameCaseInsensitive = true,
    };

    private readonly VisionClient _vision = vision;
    private readonly ILogger<AttachmentContextBuilder> _logger = logger;

    public async Task<AttachmentContext?> BuildAsync(string? imageUrl, string? pdfUrl, CancellationToken ct)
    {
        if (!string.IsNullOrWhiteSpace(imageUrl))
            return await BuildImageAsync(imageUrl!, ct);
        if (!string.IsNullOrWhiteSpace(pdfUrl))
            return await BuildPdfAsync(pdfUrl!, ct);
        return null;
    }

    // ── 图片：调 VLM 输出结构化 JSON ──
    private async Task<AttachmentContext?> BuildImageAsync(string imageUrl, CancellationToken ct)
    {
        var filePath = ResolveLocalPath(imageUrl);
        if (filePath == null || !File.Exists(filePath))
        {
            _logger.LogWarning("Image file not found for {Url}", imageUrl);
            return new AttachmentContext("image", null, null, "图片暂时无法读取", "[图片附件读取失败，请描述图片内容]");
        }

        var bytes = await File.ReadAllBytesAsync(filePath, ct);
        var mime = Path.GetExtension(filePath).ToLowerInvariant() switch
        {
            ".png" => "image/png",
            ".webp" => "image/webp",
            ".heic" => "image/heic",
            _ => "image/jpeg",
        };
        var dataUrl = $"data:{mime};base64,{Convert.ToBase64String(bytes)}";

        var prompt = """
            识别图片内容，输出 JSON：
            {"category":"food|report|wound|drug|chart|other","summary":"1-2句话描述图片","details":["关键信息1","关键信息2"]}

            分类标准：
            - food：任何食物、饮品、餐食
            - report：医学检查报告、化验单、影像报告（X光/CT/MRI/B超截图等）
            - wound：伤口、术后切口、皮肤异常
            - drug：药品包装、药品说明书
            - chart：心电图、监护仪截图等
            - other：其他

            details 给出最有价值的细节（食物种类、报告关键值、伤口部位等）。
            只输出 JSON 本身，不要任何前后缀。
            """;

        string? raw = null;
        try
        {
            var resp = await _vision.VisionAsync(prompt, [dataUrl], userText: null, maxTokens: 1024, ct: ct);
            raw = resp.Choices?.FirstOrDefault()?.Message?.Content;
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "VLM call failed for image {Url}", imageUrl);
        }

        if (string.IsNullOrWhiteSpace(raw))
            return new AttachmentContext("image", null, null, "图片识别失败", "[图片附件识别失败，请简要描述图片内容]");

        try
        {
            var cleaned = StripCodeFence(raw.Trim());
            using var doc = JsonDocument.Parse(cleaned);
            var root = doc.RootElement;
            var category = GetString(root, "category") ?? "other";
            var summary = GetString(root, "summary") ?? "图片内容";
            var details = root.TryGetProperty("details", out var d) && d.ValueKind == JsonValueKind.Array
                ? string.Join("、", d.EnumerateArray().Select(x => x.GetString()).Where(s => !string.IsNullOrWhiteSpace(s)))
                : "";

            var compact = string.IsNullOrEmpty(details)
                ? $"图片识别：{summary}"
                : $"图片识别：{summary}（{details}）";
            var llmContent = $"[图片识别（类别 {category}）：{summary}{(string.IsNullOrEmpty(details) ? "" : $"，包含 {details}")}]";

            return new AttachmentContext("image", category, null, compact, llmContent);
        }
        catch (JsonException ex)
        {
            _logger.LogWarning(ex, "Failed to parse VLM JSON: {Raw}", raw);
            // 兜底：原文也能用
            return new AttachmentContext("image", null, null, $"图片识别：{raw[..Math.Min(raw.Length, 80)]}", $"[图片识别：{raw}]");
        }
    }

    // ── PDF：PdfPig 抽取文本 ──
    private Task<AttachmentContext?> BuildPdfAsync(string pdfUrl, CancellationToken ct)
    {
        var filePath = ResolveLocalPath(pdfUrl);
        var fileName = Path.GetFileName(pdfUrl);
        if (filePath == null || !File.Exists(filePath))
        {
            _logger.LogWarning("PDF file not found for {Url}", pdfUrl);
            return Task.FromResult<AttachmentContext?>(new AttachmentContext(
                "pdf", null, fileName, "PDF 文件读取失败",
                "[PDF 附件读取失败，请描述文档内容]"));
        }

        try
        {
            using var pdf = PdfDocument.Open(filePath);
            var sb = new System.Text.StringBuilder();
            foreach (var page in pdf.GetPages())
            {
                sb.AppendLine(page.Text);
                if (sb.Length > MaxPdfChars) break;
            }
            var text = sb.ToString().Trim();

            if (string.IsNullOrWhiteSpace(text))
            {
                return Task.FromResult<AttachmentContext?>(new AttachmentContext(
                    "pdf", null, fileName, "PDF 内容为空或扫描件",
                    "[PDF 看起来是扫描件或图片版，无法提取文字。请提示用户改用拍照上传，或简要描述报告内容]"));
            }

            var truncated = text.Length > MaxPdfChars ? text[..MaxPdfChars] + "...（已截断）" : text;
            var summary = $"PDF：{fileName}";
            var llmContent = $"[用户上传 PDF 「{fileName}」，文档内容如下]\n{truncated}";

            return Task.FromResult<AttachmentContext?>(new AttachmentContext("pdf", null, fileName, summary, llmContent));
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to parse PDF {Url}", pdfUrl);
            return Task.FromResult<AttachmentContext?>(new AttachmentContext(
                "pdf", null, fileName, "PDF 解析异常",
                "[PDF 解析失败，请提示用户文件可能损坏或加密]"));
        }
    }

    private static string? ResolveLocalPath(string url)
    {
        // url 形如 "/uploads/{guid}.{ext}"。处理 base URL 前缀也兼容。
        var idx = url.IndexOf("/uploads/", StringComparison.Ordinal);
        if (idx < 0) return null;
        var relative = url[(idx + "/uploads/".Length)..];
        // 去掉可能的 query string
        var q = relative.IndexOf('?');
        if (q >= 0) relative = relative[..q];
        return Path.Combine(Directory.GetCurrentDirectory(), "uploads", relative);
    }

    private static string StripCodeFence(string raw)
    {
        var t = raw.Trim();
        if (t.StartsWith("```"))
        {
            var firstNewline = t.IndexOf('\n');
            if (firstNewline > 0) t = t[(firstNewline + 1)..];
            if (t.EndsWith("```")) t = t[..^3];
        }
        return t.Trim();
    }

    private static string? GetString(JsonElement el, string name) =>
        el.TryGetProperty(name, out var v) && v.ValueKind == JsonValueKind.String ? v.GetString() : null;
}
