import { BadRequestException } from '@nestjs/common';
import JSZip from 'jszip';
import { AiExcelReaderService } from './ai-excel-reader.service';
import { AiAttachmentService } from './ai-attachment.service';

describe('AiAttachmentService', () => {
  const repository = {
    findByIds: jest.fn(),
  };
  const service = new AiAttachmentService(repository as never, new AiExcelReaderService());

  it.each([
    [Buffer.from([0xff, 0xd8, 0xff, 0x00]), 'image/jpeg', 'image/jpeg'],
    [Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), 'image/png', 'image/png'],
    [Buffer.from('%PDF-1.7'), 'application/pdf', 'application/pdf'],
  ])('detects file signatures for %s', (buffer, declared, expected) => {
    const detectMimeType = (
      service as unknown as { detectMimeType(buffer: Buffer, declared: string): string }
    ).detectMimeType.bind(service);
    expect(detectMimeType(buffer, declared)).toBe(expected);
  });

  it('detects CSV from the declared MIME type without a binary signature', () => {
    const detectMimeType = (
      service as unknown as { detectMimeType(buffer: Buffer, declared: string): string }
    ).detectMimeType.bind(service);
    expect(detectMimeType(Buffer.from('姓名,学号\n张三,1\n'), 'text/csv')).toBe('text/csv');
    expect(detectMimeType(Buffer.from('a,b\n1,2\n'), 'application/csv')).toBe('text/csv');
  });

  it('rejects more than five attachments before repository access', async () => {
    await expect(service.requireReadyOwned(7, [1, 2, 3, 4, 5, 6])).rejects.toBeInstanceOf(
      BadRequestException,
    );
    expect(repository.findByIds).not.toHaveBeenCalled();
  });

  it('rejects image model parts when vision is disabled', async () => {
    await expect(
      service.toModelParts(
        [
          {
            id: 1,
            mimeType: 'image/png',
            originalName: 'image.png',
          } as never,
        ],
        false,
      ),
    ).rejects.toBeInstanceOf(BadRequestException);
  });

  it('rejects mismatched file extensions', () => {
    const assertFileExtension = (
      service as unknown as { assertFileExtension(name: string, mimeType: string): void }
    ).assertFileExtension.bind(service);
    expect(() => assertFileExtension('report.exe', 'application/pdf')).toThrow(BadRequestException);
    expect(() => assertFileExtension('report.pdf', 'application/pdf')).not.toThrow();
    expect(() => assertFileExtension('students.csv', 'text/csv')).not.toThrow();
    expect(() => assertFileExtension('students.xlsx', 'text/csv')).toThrow(BadRequestException);
  });

  it('decodes UTF-8 filenames mangled by Latin-1 multipart parsing', () => {
    const decodeFilename = (
      service as unknown as { decodeFilename(name: string): string }
    ).decodeFilename.bind(service);
    expect(decodeFilename('26æ\u009a\u0091æ\u009c\u009fæ\u0096\u0087å\u008c\u0096è¯¾å®¿è\u0088\u008d.xlsx')).toBe(
      '26暑期文化课宿舍.xlsx',
    );
    expect(decodeFilename('café.xlsx')).toBe('café.xlsx');
    expect(decodeFilename('暑期.xlsx')).toBe('暑期.xlsx');
  });

  it('limits the total image bytes sent to a vision model', async () => {
    await expect(
      service.toModelParts(
        [
          { id: 1, mimeType: 'image/png', originalName: 'a.png', size: 11 * 1024 * 1024 } as never,
          { id: 2, mimeType: 'image/png', originalName: 'b.png', size: 10 * 1024 * 1024 } as never,
        ],
        true,
      ),
    ).rejects.toBeInstanceOf(BadRequestException);
  });

  it('extracts text from namespace-prefixed (WPS-style) xlsx via fallback', async () => {
    const zip = new JSZip();
    zip.file(
      '[Content_Types].xml',
      `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
<Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>
</Types>`,
    );
    zip.file(
      '_rels/.rels',
      `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
</Relationships>`,
    );
    zip.file(
      'xl/workbook.xml',
      `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<x:workbook xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
<x:sheets><x:sheet name="名单" sheetId="1" state="visible" r:id="rId1"/></x:sheets>
</x:workbook>`,
    );
    zip.file(
      'xl/_rels/workbook.xml.rels',
      `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>
</Relationships>`,
    );
    zip.file(
      'xl/sharedStrings.xml',
      `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<x:sst xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><x:si><x:t>张三</x:t></x:si></x:sst>`,
    );
    zip.file(
      'xl/worksheets/sheet1.xml',
      `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<x:worksheet xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<x:sheetData>
<x:row r="1"><x:c r="A1" t="inlineStr"><x:is><x:t>姓名</x:t></x:is></x:c><x:c r="B1" t="inlineStr"><x:is><x:t>手机号</x:t></x:is></x:c></x:row>
<x:row r="2"><x:c r="A2" t="s"><x:v>0</x:v></x:c><x:c r="B2"><x:v>13800138000</x:v></x:c></x:row>
</x:sheetData>
</x:worksheet>`,
    );
    const buffer = await zip.generateAsync({ type: 'nodebuffer' });
    const extract = (
      service as unknown as {
        extractText(buffer: Buffer, mimeType: string): Promise<string | null>;
      }
    ).extractText.bind(service);
    const text = await extract(
      Buffer.from(buffer),
      'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    );
    expect(text).toContain('# 名单');
    expect(text).toContain('张三');
    expect(text).toContain('13800138000');
  });

});
