import { afterEach, describe, expect, it, vi } from 'vitest';

import {
  DocumentExtractionError,
  recognizeMaterialsFromDocument,
} from '@/services/documentExtractionService';

describe('recognizeMaterialsFromDocument', () => {
  afterEach(() => {
    vi.unstubAllGlobals();
    vi.restoreAllMocks();
  });

  it('posts the source file metadata and returns inserted procurement materials', async () => {
    const insertedMaterial = {
      id: 'mat-pdf-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: '采购清单.pdf',
      source_file_id: 'source-file-001',
      batch_id: 'CG-2026-001',
      created_at: '2026-04-26T09:00:00.000Z',
      created_by: 'user-001',
    };
    const fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify({
      success: true,
      materials: [insertedMaterial],
      summary: '提取 1 项',
      warnings: ['品牌未指定'],
      elapsed: '1.2s',
      extraction_model: 'extraction-stage-model',
    }), {
      status: 200,
      headers: { 'Content-Type': 'application/json' },
    }));
    vi.stubGlobal('fetch', fetchMock);

    const file = new File(['pdf bytes'], '采购清单.pdf', { type: 'application/pdf' });
    const result = await recognizeMaterialsFromDocument(file, {
      batchId: 'CG-2026-001',
      sourceFileId: 'source-file-001',
      sourceFileName: '采购清单.pdf',
      createdBy: 'user-001',
    });

    expect(result).toEqual({
      materials: [insertedMaterial],
      summary: '提取 1 项',
      warnings: ['品牌未指定'],
      elapsed: '1.2s',
      extractionModel: 'extraction-stage-model',
    });

    expect(fetchMock).toHaveBeenCalledWith('/api/extract-materials', {
      method: 'POST',
      body: expect.any(FormData),
    });
    const formData = fetchMock.mock.calls[0]?.[1]?.body as FormData;
    expect(formData.get('file')).toBe(file);
    expect(formData.get('batch_id')).toBe('CG-2026-001');
    expect(formData.get('source_file_id')).toBe('source-file-001');
    expect(formData.get('source_file_name')).toBe('采购清单.pdf');
    expect(formData.get('created_by')).toBe('user-001');
  });

  it('throws a typed retryable error for LLM rate limits', async () => {
    const fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify({
      success: false,
      code: 'LLM_RATE_LIMIT',
      retryable: true,
      error: 'glm-5.1 调用达到速率限制，请稍后重试',
      raw_error: '429 您的账户已达到速率限制，请您控制请求频率',
      batch_id: 'CG-2026-001',
      source_file_name: '低压热缩附件.docx',
    }), {
      status: 429,
      headers: { 'Content-Type': 'application/json' },
    }));
    vi.stubGlobal('fetch', fetchMock);

    const file = new File(['docx bytes'], '低压热缩附件.docx', {
      type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    });

    const recognition = recognizeMaterialsFromDocument(file, {
      batchId: 'CG-2026-001',
      sourceFileId: 'source-file-001',
      sourceFileName: '低压热缩附件.docx',
    });

    await expect(recognition).rejects.toBeInstanceOf(DocumentExtractionError);
    await expect(recognition).rejects.toMatchObject({
      name: 'DocumentExtractionError',
      message: '模型调用达到速率限制，文件已保留为附件，请稍后重试识别。',
      status: 429,
      code: 'LLM_RATE_LIMIT',
      retryable: true,
      batchId: 'CG-2026-001',
      sourceFileName: '低压热缩附件.docx',
    });
  });

  it('accepts legacy items responses while the backend response contract is unified', async () => {
    const insertedMaterial = {
      id: 'mat-doc-001',
      material_name: '桥架',
      specification: '',
      quantity: 6,
      unit: '米',
      technical_standard: '',
      material_requirement: '',
      technical_requirement: '',
      applicable_conditions: '',
      test_requirement: '',
      acceptance_note: '',
      warranty_period: '',
      deadline: '',
      invoice_type: '',
      brand: '',
      remarks: '',
      delivery_address: '',
      source_file: '清单.pdf',
      source_file_id: 'source-file-001',
      batch_id: 'CG-2026-001',
      created_at: '2026-04-26T09:00:00.000Z',
    };
    const fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify({
      success: true,
      items: [insertedMaterial],
      summary: '提取 1 项',
      elapsed: '1.0s',
    }), {
      status: 200,
      headers: { 'Content-Type': 'application/json' },
    }));
    vi.stubGlobal('fetch', fetchMock);

    const result = await recognizeMaterialsFromDocument(
      new File(['pdf bytes'], '清单.pdf', { type: 'application/pdf' }),
      {
        batchId: 'CG-2026-001',
        sourceFileId: 'source-file-001',
        sourceFileName: '清单.pdf',
      },
    );

    expect(result.materials).toEqual([insertedMaterial]);
  });
});
