import { describe, expect, it, vi } from "vitest";

import { extractAndInsertMaterialsFromUploadedFile } from "./extract-materials-api.ts";
import type { DocumentMaterialExtractionResult } from "../standardization/document-extraction-llm.ts";

function makeExtractionResult(materialName = "球墨铸铁井盖"): DocumentMaterialExtractionResult {
  return {
    items: [
      {
        material_name: materialName,
        specification: "DN700",
        quantity: 2,
        unit: "套",
        technical_standard: "",
        material_requirement: "",
        technical_requirement: "",
        applicable_conditions: "",
        test_requirement: "",
        acceptance_note: "",
        warranty_period: "",
        deadline: "",
        invoice_type: "",
        brand: "",
        remarks: "",
        delivery_address: "",
      },
    ],
    summary: "提取 1 项",
    warnings: ["品牌未指定"],
    model: "extraction-stage-model",
    provider: "glm",
  };
}

function makeDbMock(insertedRows: unknown[]) {
  const select = vi.fn().mockResolvedValue({ data: insertedRows, error: null });
  const insert = vi.fn(() => ({ select }));
  const from = vi.fn(() => ({ insert }));
  return { from, insert, select };
}

function makeDbInsertErrorMock(message = "insert failed") {
  const select = vi.fn().mockResolvedValue({ data: null, error: { message } });
  const insert = vi.fn(() => ({ select }));
  const from = vi.fn(() => ({ insert }));
  return { from, insert, select };
}

describe("extractAndInsertMaterialsFromUploadedFile", () => {
  it.each([
    ["PDF", "application/pdf", "采购清单.pdf"],
    ["image", "image/png", "采购清单.png"],
    ["JPEG image", "image/jpeg", "采购清单.jpg"],
    ["Word", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "采购清单.docx"],
  ])("extracts %s files with the document extraction LLM and inserts the normalized procurement materials", async (_label, mimeType, sourceFileName) => {
    const insertedRows = [{
      id: "mat-001",
      material_name: "球墨铸铁井盖",
      specification: "DN700",
      quantity: 2,
      unit: "套",
      technical_standard: "",
      material_requirement: "",
      technical_requirement: "",
      applicable_conditions: "",
      test_requirement: "",
      acceptance_note: "",
      warranty_period: "",
      deadline: "",
      invoice_type: "",
      brand: "",
      remarks: "",
      delivery_address: "",
      source_file: sourceFileName,
      source_file_id: "source-file-001",
      batch_id: "batch-001",
      created_by: "user-001",
      created_at: "2026-04-26T09:00:00.000Z",
    }];
    const db = makeDbMock(insertedRows);
    const extractMaterials = vi.fn().mockResolvedValue(makeExtractionResult());

    const result = await extractAndInsertMaterialsFromUploadedFile({
      fileBuffer: Buffer.from("file bytes"),
      mimeType,
      batchId: "batch-001",
      sourceFileId: "source-file-001",
      sourceFileName,
      createdBy: "user-001",
      extractMaterials,
      db: db as never,
    });

    expect(extractMaterials).toHaveBeenCalledWith({
      fileBuffer: Buffer.from("file bytes"),
      mimeType,
      batchId: "batch-001",
      sourceFileName,
    });
    expect(db.from).toHaveBeenCalledWith("procurement_materials");
    expect(db.insert).toHaveBeenCalledWith([expect.objectContaining({
      material_name: "球墨铸铁井盖",
      source_file: sourceFileName,
      source_file_id: "source-file-001",
      batch_id: "batch-001",
      created_by: "user-001",
    })]);
    expect(result).toEqual({
      success: true,
      materials: insertedRows,
      extraction_summary: "提取 1 项",
      summary: "提取 1 项",
      warnings: ["品牌未指定"],
      extraction_model: "extraction-stage-model",
      extraction_provider: "glm",
    });
  });

  it("does not insert procurement materials when GLM extraction fails", async () => {
    const db = makeDbMock([]);
    const extractMaterials = vi.fn().mockRejectedValue(new Error("文档提取模型返回内容不是合法 JSON"));

    await expect(extractAndInsertMaterialsFromUploadedFile({
      fileBuffer: Buffer.from("file bytes"),
      mimeType: "application/pdf",
      batchId: "batch-001",
      sourceFileId: "source-file-001",
      sourceFileName: "采购清单.pdf",
      extractMaterials,
      db: db as never,
    })).rejects.toThrow(/不是合法 JSON/);

    expect(db.from).not.toHaveBeenCalled();
    expect(db.insert).not.toHaveBeenCalled();
  });

  it("does not insert procurement materials when image OCR fails before extraction", async () => {
    const db = makeDbMock([]);
    const extractMaterials = vi.fn().mockRejectedValue(new Error("文档 OCR 返回内容为空"));

    await expect(extractAndInsertMaterialsFromUploadedFile({
      fileBuffer: Buffer.from("image bytes"),
      mimeType: "image/png",
      batchId: "batch-001",
      sourceFileId: "source-file-001",
      sourceFileName: "采购清单.png",
      extractMaterials,
      db: db as never,
    })).rejects.toThrow(/文档 OCR 返回内容为空/);

    expect(db.from).not.toHaveBeenCalled();
    expect(db.insert).not.toHaveBeenCalled();
  });

  it("does not insert procurement materials when GLM extraction times out", async () => {
    const db = makeDbMock([]);
    const extractMaterials = vi.fn().mockRejectedValue(new Error("模型请求超时"));

    await expect(extractAndInsertMaterialsFromUploadedFile({
      fileBuffer: Buffer.from("file bytes"),
      mimeType: "application/pdf",
      batchId: "batch-001",
      sourceFileId: "source-file-001",
      sourceFileName: "采购清单.pdf",
      extractMaterials,
      db: db as never,
    })).rejects.toThrow(/模型请求超时/);

    expect(db.from).not.toHaveBeenCalled();
    expect(db.insert).not.toHaveBeenCalled();
  });

  it("surfaces DB insert failures after successful GLM extraction", async () => {
    const db = makeDbInsertErrorMock("permission denied");
    const extractMaterials = vi.fn().mockResolvedValue(makeExtractionResult());

    await expect(extractAndInsertMaterialsFromUploadedFile({
      fileBuffer: Buffer.from("file bytes"),
      mimeType: "application/pdf",
      batchId: "batch-001",
      sourceFileId: "source-file-001",
      sourceFileName: "采购清单.pdf",
      extractMaterials,
      db: db as never,
    })).rejects.toThrow(/\[procurement_materials\] 写入文档识别物料失败: permission denied/);

    expect(db.from).toHaveBeenCalledWith("procurement_materials");
    expect(db.insert).toHaveBeenCalledWith([expect.objectContaining({
      material_name: "球墨铸铁井盖",
      source_file_id: "source-file-001",
    })]);
  });
});
