/**
 * ============================================================
 * document-extraction-llm.ts — 文档提取 LLM 函数
 * ============================================================
 * 功能：
 *   1. convertDocumentToMarkdown() — 调用 markitdown CLI 将文件转为 markdown
 *   2. extractMaterialsFromMarkdown() — 调用 LLM 从 markdown 中提取采购物料
 * ============================================================
 */

import { execFile } from "node:child_process";
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import path from "node:path";
import { promisify } from "node:util";
import { fileURLToPath } from "node:url";

import type { RawCsvRow } from "../core/types.ts";
import {
  createLlmClient,
  resolveLlmRuntimeConfig,
} from "../core/llm-client.ts";
import { resolveTextPrompt } from "../internal/prompt-runtime.js";
import { DOCUMENT_EXTRACTION_PROMPT_KEY } from "./document-extraction-contract.ts";

const execFileAsync = promisify(execFile);
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const PROJECT_ROOT = path.resolve(__dirname, "..", "..");
const MARKITDOWN_VENV_PYTHON = path.join(PROJECT_ROOT, "markitdown", ".venv", "bin", "python3");
const CONVERT_SCRIPT = path.join(PROJECT_ROOT, "markitdown", "convert_with_ocr.py");
const OUTPUT_DIR = path.join(PROJECT_ROOT, "output");

// ── 日志工具 ──────────────────────────────────────────────────

function ensureOutputDir(subDir: string): string {
  const dir = path.join(OUTPUT_DIR, subDir);
  if (!existsSync(dir)) {
    mkdirSync(dir, { recursive: true });
  }
  return dir;
}

function writeOutputFile(subDir: string, fileName: string, content: string): string {
  const dir = ensureOutputDir(subDir);
  const filePath = path.join(dir, fileName);
  writeFileSync(filePath, content, "utf-8");
  return filePath;
}

// ── markitdown 转换 ───────────────────────────────────────────

export interface ConvertResult {
  markdown: string;
  outputPath: string;
}

/**
 * 调用 markitdown CLI 将文件转为 markdown。
 * markdown 输出到本地 output/{batchId}/{originalName}.md，不写库。
 */
export async function convertDocumentToMarkdown(
  filePath: string,
  batchId: string,
  originalFileName: string,
): Promise<ConvertResult> {
  if (!existsSync(MARKITDOWN_VENV_PYTHON)) {
    throw new Error(
      `[文档转换] 找不到 markitdown Python 环境: ${MARKITDOWN_VENV_PYTHON}\n` +
      `请执行: cd markitdown && uv sync`,
    );
  }

  console.log(`[文档转换] 开始转换: ${originalFileName} (batch: ${batchId})`);

  // 读取 LLM 配置用于图片 OCR（智谱 GLM-4V 视觉模型）
  let llmBaseUrl = "";
  let llmApiKey = "";
  let llmModel = "glm-4v-plus";
  let ocrPrompt = "";
  try {
    const config = await resolveLlmRuntimeConfig();
    llmBaseUrl = config.baseURL;
    llmApiKey = config.apiKey;
    // 图片 OCR 需要视觉模型，如果当前配置的不是视觉模型则用默认值
    llmModel = "glm-4v-plus";
    ocrPrompt = await resolveDocumentOcrPrompt(config.provider, llmModel);
    console.log(`[文档转换] OCR LLM: provider=${config.provider}, vision_model=${llmModel}`);
  } catch (err) {
    console.warn(`[文档转换] 无法获取 LLM 配置或 OCR prompt，图片 OCR 将不可用:`, err);
  }

  try {
    const args = [CONVERT_SCRIPT, filePath];
    if (llmBaseUrl && llmApiKey) {
      args.push("--base-url", llmBaseUrl, "--api-key", llmApiKey, "--model", llmModel);
      if (ocrPrompt) {
        args.push("--ocr-prompt", ocrPrompt);
      }
    }

    const { stdout, stderr } = await execFileAsync(
      MARKITDOWN_VENV_PYTHON,
      args,
      {
        timeout: 120_000, // 2 分钟超时
        maxBuffer: 50 * 1024 * 1024, // 50MB
        env: { ...process.env, PYTHONIOENCODING: "utf-8" },
      },
    );

    if (stderr) {
      console.warn(`[文档转换] markitdown stderr: ${stderr.slice(0, 500)}`);
    }

    const markdown = stdout;

    if (!markdown || markdown.trim().length === 0) {
      throw new Error(`markitdown 转换结果为空，可能文件格式不支持或内容无法识别`);
    }

    // 写入本地 output 目录
    const mdFileName = originalFileName.replace(/\.[^.]+$/, ".md");
    const outputPath = writeOutputFile(batchId, mdFileName, markdown);
    console.log(`[文档转换] 转换完成: ${outputPath} (${markdown.length} 字符)`);

    // 写转换日志
    const logContent = [
      `转换时间: ${new Date().toISOString()}`,
      `原始文件: ${originalFileName}`,
      `批次: ${batchId}`,
      `Markdown 长度: ${markdown.length} 字符`,
      `---`,
      stderr ? `stderr:\n${stderr}` : "(无 stderr)",
    ].join("\n");
    writeOutputFile(batchId, `${originalFileName}.convert.log`, logContent);

    return { markdown, outputPath };
  } catch (error: unknown) {
    const message = error instanceof Error ? error.message : String(error);
    console.error(`[文档转换] 转换失败: ${originalFileName}`, message);

    // 写错误日志
    writeOutputFile(batchId, `${originalFileName}.convert-error.log`, [
      `转换时间: ${new Date().toISOString()}`,
      `原始文件: ${originalFileName}`,
      `错误: ${message}`,
    ].join("\n"));

    throw new Error(`文档转换失败 (${originalFileName}): ${message}`);
  }
}

// ── LLM 提取 ─────────────────────────────────────────────────

interface ExtractionResult {
  items: RawCsvRow[];
  summary: string;
}

const DOCUMENT_OCR_PROMPT_KEY = "automation.document_ocr.image_text_extraction.user";

async function resolveDocumentExtractionSystemPrompt(provider: string, model: string): Promise<string> {
  const prompt = await resolveTextPrompt(DOCUMENT_EXTRACTION_PROMPT_KEY, {
    provider: provider || process.env.PROMPT_PROVIDER || process.env.LLM_PROVIDER || "*",
    model: model || process.env.LLM_MODEL || "*",
  });

  return prompt.text;
}

async function resolveDocumentOcrPrompt(provider: string, model: string): Promise<string> {
  const prompt = await resolveTextPrompt(DOCUMENT_OCR_PROMPT_KEY, {
    provider: provider || process.env.PROMPT_PROVIDER || process.env.LLM_PROVIDER || "*",
    model: model || process.env.LLM_MODEL || "*",
  });

  return prompt.text;
}

/**
 * 调用 LLM 从 markdown 文本中提取采购物料列表。
 * 复用现有的 LLM 配置（智谱 GLM / 环境变量配置）。
 */
export async function extractMaterialsFromMarkdown(
  markdown: string,
  batchId: string,
  originalFileName: string,
): Promise<ExtractionResult> {
  const { client, config } = await createLlmClient({ maxRetries: 2 });
  const systemPrompt = await resolveDocumentExtractionSystemPrompt(config.provider, config.model);
  console.log(`[文档提取] 使用 LLM: provider=${config.provider}, model=${config.model}`);

  // 截断处理：GLM-4 系列最大 128K，保守取 80K 字符
  const MAX_CHARS = 80_000;
  let truncated = false;
  let inputMarkdown = markdown;
  if (markdown.length > MAX_CHARS) {
    inputMarkdown = markdown.slice(0, MAX_CHARS);
    truncated = true;
    console.warn(`[文档提取] 文档过长 (${markdown.length} 字符)，已截断至 ${MAX_CHARS} 字符`);
  }

  console.log(`[文档提取] 开始提取: ${originalFileName} (${inputMarkdown.length} 字符)`);

  try {
    const response = await client.chat.completions.create({
      model: config.model,
      messages: [
        { role: "system", content: systemPrompt },
        { role: "user", content: inputMarkdown },
      ],
      temperature: 0.1,
      response_format: { type: "json_object" },
    });

    const content = response.choices[0]?.message?.content ?? "";

    // 写 LLM 原始响应日志
    writeOutputFile(batchId, `${originalFileName}.llm-response.json`, content);

    if (!content.trim()) {
      throw new Error("LLM 返回内容为空");
    }

    const parsed = JSON.parse(content) as {
      items?: Array<Record<string, string>>;
      extraction_summary?: string;
    };

    if (!parsed.items || !Array.isArray(parsed.items)) {
      throw new Error(`LLM 返回格式错误，缺少 items 数组。原始内容: ${content.slice(0, 200)}`);
    }

    // 转换为 RawCsvRow 格式
    const items: RawCsvRow[] = parsed.items
      .filter((item) => {
        const name = String(item["名称"] ?? "").trim();
        return name.length > 0;
      })
      .map((item) => ({
        发票种类: String(item["发票种类"] ?? "").trim(),
        名称: String(item["名称"] ?? "").trim(),
        规格型号: String(item["规格型号"] ?? "").trim(),
        品牌: String(item["品牌"] ?? "").trim(),
        数量: String(item["数量"] ?? "").trim(),
        单位: String(item["单位"] ?? "").trim(),
        "备注/附件": String(item["备注/附件"] ?? "").trim(),
        收货地址: String(item["收货地址"] ?? "").trim(),
        技术标准: String(item["技术标准"] ?? "").trim(),
        材质要求: String(item["材质要求"] ?? "").trim(),
        技术要求: String(item["技术要求"] ?? "").trim(),
        适用条件: String(item["适用条件"] ?? "").trim(),
        检验要求: String(item["检验要求"] ?? "").trim(),
        验收说明: String(item["验收说明"] ?? "").trim(),
        质保期: String(item["质保期"] ?? "").trim(),
        交货期限: String(item["交货期限"] ?? "").trim(),
      }));

    const summary = parsed.extraction_summary ?? `提取了 ${items.length} 项物料`;
    console.log(`[文档提取] 提取完成: ${items.length} 项物料。${truncated ? "(文档已截断)" : ""} ${summary}`);

    // 写提取结果日志
    writeOutputFile(batchId, `${originalFileName}.extraction-result.json`, JSON.stringify({
      totalItems: items.length,
      truncated,
      summary,
      items,
    }, null, 2));

    return { items, summary };
  } catch (error: unknown) {
    const message = error instanceof Error ? error.message : String(error);
    console.error(`[文档提取] LLM 提取失败: ${originalFileName}`, message);

    // 写错误日志
    writeOutputFile(batchId, `${originalFileName}.extraction-error.log`, [
      `提取时间: ${new Date().toISOString()}`,
      `原始文件: ${originalFileName}`,
      `LLM: ${config.provider}/${config.model}`,
      `错误: ${message}`,
    ].join("\n"));

    throw new Error(`物料提取失败 (${originalFileName}): ${message}`);
  }
}
