using Health.Application.Reports;
using Health.Application.Notifications;
using Health.Infrastructure.AI;
using Microsoft.Extensions.Logging;
using UglyToad.PdfPig;

namespace Health.Infrastructure.Reports;

public sealed class ReportAnalysisService(
    IReportRepository reports,
    VisionClient vision,
    DeepSeekClient llm,
    IUserNotificationProducer notifications,
    ILogger<ReportAnalysisService> logger) : IReportAnalysisService
{
    private readonly IReportRepository _reports = reports;
    private readonly VisionClient _vision = vision;
    private readonly DeepSeekClient _llm = llm;
    private readonly IUserNotificationProducer _notifications = notifications;
    private readonly ILogger<ReportAnalysisService> _logger = logger;
    private const int MaxPdfChars = 8000;

    public async Task AnalyzeAsync(ReportAnalysisJob job, CancellationToken ct)
    {
        var report = await _reports.GetByIdAsync(job.ReportId, ct);
        if (report == null) return;

        try
        {
            var vlmJson = await ExtractIndicatorsAsync(job.FilePath, ct);

            var isReport = vlmJson.TryGetProperty("isReport", out var ir) && ir.GetBoolean();
            var reason = vlmJson.TryGetProperty("reason", out var rsn) ? rsn.GetString() : null;
            if (!isReport)
            {
                report.AiSummary = $"AI 暂时无法完成解读：{reason ?? "这可能不是一份医学报告"}。请重新上传清晰的报告图片。";
                report.AiIndicators = "[]";
                report.Status = ReportStatus.AnalysisFailed;
                await _reports.SaveChangesAsync(ct);
                await NotifyAsync(report, job.TaskId, false, ct);
                return;
            }

            var reportType = vlmJson.TryGetProperty("reportType", out var rt) ? rt.GetString() ?? "Other" : "Other";
            var indicatorsJson = vlmJson.TryGetProperty("indicators", out var inds) ? inds.GetRawText() : "[]";

            report.Category = Enum.TryParse<ReportCategory>(reportType, out var cat) ? cat : ReportCategory.Other;
            report.AiIndicators = indicatorsJson;
            report.AiSummary = await GenerateSummaryAsync(indicatorsJson, ct);
            report.Status = ReportStatus.PendingDoctor;

            await _reports.SaveChangesAsync(ct);
            await NotifyAsync(report, job.TaskId, true, ct);
            _logger.LogInformation("报告 AI 分析完成: {ReportId}", job.ReportId);
        }
        catch (OperationCanceledException) when (ct.IsCancellationRequested)
        {
            // 进程停机导致的取消：不算失败，不消耗重试，交还给恢复机制
            throw;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "报告 AI 分析失败: {ReportId}", job.ReportId);
            report.Status = ReportStatus.AnalysisFailed;
            report.AiSummary = "AI 暂时无法完成解读，可能是图片不清晰或服务暂时不可用。请稍后重试，或重新上传更清晰的报告图片。";
            report.AiIndicators = "[]";
            await _reports.SaveChangesAsync(ct);

            // 重新抛出，使 Worker 进入 RetryAsync（指数退避重试，超限后任务置 Failed）；
            // 若为可恢复的瞬时故障，重试成功会把状态改回 PendingDoctor。
            throw;
        }
    }

    private async Task<JsonElement> ExtractIndicatorsAsync(string filePath, CancellationToken ct)
    {
        if (Path.GetExtension(filePath).Equals(".pdf", StringComparison.OrdinalIgnoreCase))
            return await ExtractIndicatorsFromPdfAsync(filePath, ct);

        var imageUrl = await GetLocalImageUrl(filePath, ct);
        if (imageUrl == null)
            throw new InvalidOperationException("报告图片文件不存在或无法读取");

        var prompt = """
            你是一名医学检验报告分析专家。请仔细识别这份医学报告。

            第一步：判断这到底是不是一份医学检验报告。
            如果不是（比如是自拍、风景照、食物、文件等），直接返回：
            {"isReport": false, "reason": "这不是医学报告，原因：..."}

            如果是医学报告，提取所有检测指标，返回严格JSON格式（不要markdown包裹）：
            {
              "isReport": true,
              "reportType": "BloodTest/Biochemistry/Ecg/Ultrasound/Discharge/Other",
              "indicators": [
                {"name": "指标名称", "value": "数值", "unit": "单位", "referenceRange": "参考范围", "status": "normal/high/low"}
              ]
            }

            注意：
            - status 判断：数值在参考范围内=normal，超出上限=high，低于下限=low
            - 如果参考范围不明确，根据常见医学标准判断
            """;

        var response = await _vision.VisionAsync(
            prompt,
            [imageUrl],
            userText: "请分析这份医学报告",
            maxTokens: 2048,
            ct: ct);

        var content = response.Choices?.FirstOrDefault()?.Message?.Content ?? "";
        // 不记录 VLM 返回正文：可能包含检验指标等敏感医疗信息，生产日志只记录长度
        _logger.LogInformation("报告 VLM 返回长度: {Length}", content.Length);

        var json = TryParseJson(content);
        return json ?? throw new InvalidOperationException("报告识别结果格式异常");
    }

    private async Task<JsonElement> ExtractIndicatorsFromPdfAsync(string filePath, CancellationToken ct)
    {
        var text = ExtractPdfText(filePath);
        if (string.IsNullOrWhiteSpace(text))
        {
            return JsonDocument.Parse("""
                {"isReport": false, "reason": "这份 PDF 可能是扫描件或图片版，当前无法直接提取文字。请上传清晰图片，或后续启用 PDF 页转图片后再用 VLM 识别。"}
                """).RootElement;
        }

        var prompt = $$"""
            你是一名医学检验报告分析专家。下面是用户上传 PDF 中提取出的文字。

            PDF 文本：
            {{text}}

            第一步：判断这到底是不是一份医学检验报告。
            如果不是，直接返回：
            {"isReport": false, "reason": "这不是医学报告，原因：..."}

            如果是医学报告，提取所有检测指标，返回严格 JSON 格式（不要 markdown 包裹）：
            {
              "isReport": true,
              "reportType": "BloodTest/Biochemistry/Ecg/Ultrasound/Discharge/Other",
              "indicators": [
                {"name": "指标名称", "value": "数值", "unit": "单位", "referenceRange": "参考范围", "status": "normal/high/low"}
              ]
            }

            注意：
            - 只提取报告里真实出现的指标
            - status 判断：数值在参考范围内=normal，超出上限=high，低于下限=low
            - 不确定的参考范围不要编造，可留空
            """;

        var messages = new List<ChatMessage>
        {
            new() { Role = "system", Content = "你是医学报告结构化抽取助手，只输出严格 JSON。" },
            new() { Role = "user", Content = prompt }
        };

        var response = await _llm.ChatAsync(messages, maxTokens: 1800, temperature: 0.1f, ct: ct);
        var content = response.Choices?.FirstOrDefault()?.Message?.Content ?? "";
        var json = TryParseJson(content);
        return json ?? throw new InvalidOperationException("PDF 报告解析结果格式异常");
    }

    private static string ExtractPdfText(string filePath)
    {
        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();
        return text.Length > MaxPdfChars ? text[..MaxPdfChars] : text;
    }

    private async Task<string> GenerateSummaryAsync(string indicatorsJson, CancellationToken ct)
    {
        var prompt = $"""
            你是患者端医学报告预解读助手。请根据以下检验指标，用通俗易懂的语言写一份报告解读。

            检测指标：{indicatorsJson}

            要求：
            1. 总字数200-300字
            2. 先总结整体情况
            3. 指出异常指标及其可能原因，但不要给出确定诊断
            4. 给出生活方式和复查/就医沟通建议，不要给出处方、停药、换药或调药建议
            5. 如指标明显异常或存在急症风险，优先建议及时就医
            6. 末尾提醒"以上为AI预解读，不能替代医生诊断和治疗建议"

            只返回解读文本，不要JSON格式。
            """;

        var messages = new List<ChatMessage>
        {
            new() { Role = "system", Content = "你是患者端医学报告预解读助手，不替代医生诊断、处方或治疗决策。" },
            new() { Role = "user", Content = prompt }
        };

        var response = await _llm.ChatAsync(messages, maxTokens: 800, temperature: 0.3f, ct: ct);
        var summary = response.Choices?.FirstOrDefault()?.Message?.Content?.Trim() ?? "";
        return !string.IsNullOrWhiteSpace(summary)
            ? summary
            : throw new InvalidOperationException("AI 未返回有效解读内容");
    }

    private static async Task<string?> GetLocalImageUrl(string filePath, CancellationToken ct)
    {
        if (!File.Exists(filePath)) return null;

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

    private static JsonElement? TryParseJson(string? content)
    {
        if (string.IsNullOrWhiteSpace(content)) return null;

        content = content.Trim();
        if (content.StartsWith("```"))
        {
            var end = content.IndexOf('\n');
            if (end > 0) content = content[(end + 1)..];
            if (content.EndsWith("```"))
                content = content[..^3];
        }

        content = content.Trim();
        try { return JsonDocument.Parse(content).RootElement; }
        catch (JsonException) { return null; }
    }

    private Task NotifyAsync(Report report, Guid sourceId, bool succeeded, CancellationToken ct) =>
        _notifications.EnqueueAsync(
            report.UserId,
            sourceId,
            new NotificationMessage(
                succeeded ? "ReportAnalysisCompleted" : "ReportAnalysisFailed",
                succeeded ? "报告分析完成" : "报告分析未完成",
                succeeded ? "您的报告已完成 AI 预解读，点击查看分析结果。" : "这份报告暂时无法完成分析，您可以进入报告页面重新尝试。",
                succeeded ? "success" : "warning",
                "report",
                report.Id.ToString()),
            ct);
}
