import { describe, expect, it } from 'vitest';
import { looksLikeBinaryBytes, decodeText } from './binary-detect.js';

const b = (...bytes: number[]) => Buffer.from(bytes);
const txt = (s: string) => Buffer.from(s, 'utf-8');

describe('looksLikeBinaryBytes — magic signatures (binary)', () => {
  it('detects OLE2 (.xls/.doc)', () => {
    expect(looksLikeBinaryBytes(b(0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1, 0x00, 0x01)))
      .toEqual({ binary: true, reason: 'magic:ole2' });
  });
  it('detects ZIP (.xlsx/.docx)', () => {
    expect(looksLikeBinaryBytes(b(0x50, 0x4B, 0x03, 0x04, 0x14, 0x00)).binary).toBe(true);
  });
  it('detects PDF', () => {
    expect(looksLikeBinaryBytes(txt('%PDF-1.7\n...')).binary).toBe(true);
  });
  it('detects PNG / JPEG / GIF / gzip / RIFF', () => {
    expect(looksLikeBinaryBytes(b(0x89, 0x50, 0x4E, 0x47)).binary).toBe(true);
    expect(looksLikeBinaryBytes(b(0xFF, 0xD8, 0xFF, 0xE0)).binary).toBe(true);
    expect(looksLikeBinaryBytes(b(0x47, 0x49, 0x46, 0x38)).binary).toBe(true);
    expect(looksLikeBinaryBytes(b(0x1F, 0x8B, 0x08)).binary).toBe(true);
    expect(looksLikeBinaryBytes(b(0x52, 0x49, 0x46, 0x46, 1, 2, 3, 4, 0x57, 0x45, 0x42, 0x50)).binary).toBe(true);
  });
  it('RIFF reason is magic:riff', () => {
    expect(looksLikeBinaryBytes(b(0x52, 0x49, 0x46, 0x46, 1, 2, 3, 4, 0x57, 0x45, 0x42, 0x50)))
      .toEqual({ binary: true, reason: 'magic:riff' });
  });
});

describe('looksLikeBinaryBytes — byte heuristics', () => {
  it('NUL byte in head => binary', () => {
    expect(looksLikeBinaryBytes(b(0x68, 0x69, 0x00, 0x68))).toEqual({ binary: true, reason: 'nul-byte' });
  });
  it('invalid UTF-8 => binary', () => {
    expect(looksLikeBinaryBytes(b(0xC0, 0xC1, 0x80, 0x81, 0xF8, 0xF9)).binary).toBe(true);
  });
  it('high control-char ratio => binary', () => {
    expect(looksLikeBinaryBytes(b(0x01, 0x02, 0x03, 0x04, 0x05, 0x41)).binary).toBe(true);
  });
});

describe('looksLikeBinaryBytes — text (must NOT false-positive)', () => {
  it('plain UTF-8 HTML', () => {
    expect(looksLikeBinaryBytes(txt('<html><body>hi</body></html>'))).toEqual({ binary: false, encoding: 'utf-8' });
  });
  it('JSON and CSV', () => {
    expect(looksLikeBinaryBytes(txt('{"a":1,"b":[2,3]}')).binary).toBe(false);
    expect(looksLikeBinaryBytes(txt('col1,col2\n1,2\n3,4\n')).binary).toBe(false);
  });
  it('SVG / XML stays text', () => {
    expect(looksLikeBinaryBytes(txt('<?xml version="1.0"?><svg></svg>')).binary).toBe(false);
  });
  it('emoji-heavy UTF-8 stays text', () => {
    expect(looksLikeBinaryBytes(txt('hello 😀🎉🚀 world ✨')).binary).toBe(false);
  });
  it('UTF-8 BOM => text(utf-8)', () => {
    expect(looksLikeBinaryBytes(b(0xEF, 0xBB, 0xBF, 0x68, 0x69))).toEqual({ binary: false, encoding: 'utf-8' });
  });
  it('UTF-16LE BOM (contains NUL) => text(utf-16le), NOT blocked', () => {
    expect(looksLikeBinaryBytes(b(0xFF, 0xFE, 0x68, 0x00, 0x69, 0x00)))
      .toEqual({ binary: false, encoding: 'utf-16le' });
  });
  it('UTF-16BE BOM => text(utf-16be)', () => {
    expect(looksLikeBinaryBytes(b(0xFE, 0xFF, 0x00, 0x68, 0x00, 0x69)))
      .toEqual({ binary: false, encoding: 'utf-16be' });
  });
  it('does not flag a clean 8KB cut through a multibyte char', () => {
    const head = Buffer.concat([Buffer.alloc(4095, 0x61), b(0xC3)]);
    expect(looksLikeBinaryBytes(head).binary).toBe(false);
  });
  it('empty buffer => text', () => {
    expect(looksLikeBinaryBytes(Buffer.alloc(0))).toEqual({ binary: false, encoding: 'utf-8' });
  });
});

describe('decodeText', () => {
  it('decodes valid utf-8', () => {
    expect(decodeText(txt('héllo 😀'), 'utf-8')).toBe('héllo 😀');
  });
  it('returns null on invalid utf-8', () => {
    expect(decodeText(b(0x41, 0xC0, 0xC1, 0x42), 'utf-8')).toBeNull();
  });
  it('tolerates a truncated trailing multibyte sequence', () => {
    expect(decodeText(Buffer.concat([txt('a'), b(0xC3)]), 'utf-8')).toBe('a');
  });
  it('decodes utf-16le', () => {
    expect(decodeText(b(0x68, 0x00, 0x69, 0x00), 'utf-16le')).toBe('hi');
  });
  it('decodes utf-16be (or byte-swap fallback)', () => {
    expect(decodeText(b(0x00, 0x68, 0x00, 0x69), 'utf-16be')).toBe('hi');
  });
});
