import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { mkdtempSync, rmSync, writeFileSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import ExcelJS from 'exceljs';
import {
  getSpreadsheetPreview,
  getPresentationPreview,
  getDocumentPreview,
  isSpreadsheetFile,
  isPresentationFile,
  isDocumentFile,
  isOfficePreviewFile,
  SofficeUnavailableError,
} from './office-preview.js';

describe('office-preview ファイル判定', () => {
  it('Excel は xlsx/xlsm のみ (csv/xls は対象外)', () => {
    expect(isSpreadsheetFile('a.xlsx')).toBe(true);
    expect(isSpreadsheetFile('a.XLSM')).toBe(true);
    expect(isSpreadsheetFile('a.csv')).toBe(false);
    expect(isSpreadsheetFile('a.xls')).toBe(false);
  });
  it('PowerPoint は pptx/ppt', () => {
    expect(isPresentationFile('deck.pptx')).toBe(true);
    expect(isPresentationFile('deck.PPT')).toBe(true);
    expect(isPresentationFile('deck.key')).toBe(false);
  });
  it('Word は docx のみ (旧 .doc は対象外)', () => {
    expect(isDocumentFile('report.docx')).toBe(true);
    expect(isDocumentFile('report.DOCX')).toBe(true);
    expect(isDocumentFile('report.doc')).toBe(false);
    expect(isDocumentFile('report.odt')).toBe(false);
  });
  it('isOfficePreviewFile は Excel/PowerPoint/Word を拾う', () => {
    expect(isOfficePreviewFile('a.xlsx')).toBe(true);
    expect(isOfficePreviewFile('a.pptx')).toBe(true);
    expect(isOfficePreviewFile('a.docx')).toBe(true);
    expect(isOfficePreviewFile('a.txt')).toBe(false);
    expect(isOfficePreviewFile('a.doc')).toBe(false);
  });
});

describe('getSpreadsheetPreview', () => {
  let dir: string;
  let xlsxPath: string;

  beforeAll(async () => {
    dir = mkdtempSync(join(tmpdir(), 'office-preview-test-'));
    xlsxPath = join(dir, 'sample.xlsx');
    const wb = new ExcelJS.Workbook();
    const ws1 = wb.addWorksheet('データ');
    ws1.addRow(['名前', '年齢', '登録日']);
    ws1.addRow(['田中', 30, new Date('2026-01-15T00:00:00Z')]);
    ws1.addRow(['佐藤', 25, '']);
    const ws2 = wb.addWorksheet('集計');
    ws2.addRow(['合計']);
    ws2.getCell('A2').value = { formula: 'SUM(1,2)', result: 3 } as ExcelJS.CellValue;
    await wb.xlsx.writeFile(xlsxPath);
  });

  afterAll(() => {
    rmSync(dir, { recursive: true, force: true });
  });

  it('全シートをセル文字列の二次元配列で返す', async () => {
    const preview = await getSpreadsheetPreview(xlsxPath);
    expect(preview.kind).toBe('spreadsheet');
    expect(preview.sheets.map((s) => s.name)).toEqual(['データ', '集計']);
    const first = preview.sheets[0];
    expect(first.rows[0]).toEqual(['名前', '年齢', '登録日']);
    expect(first.rows[1][0]).toBe('田中');
    expect(first.rows[1][1]).toBe('30');
  });

  it('数式セルは計算結果を文字列で返す', async () => {
    const preview = await getSpreadsheetPreview(xlsxPath);
    const summary = preview.sheets.find((s) => s.name === '集計')!;
    expect(summary.rows[1][0]).toBe('3');
  });
});

describe('getPresentationPreview', () => {
  it('soffice が無い環境では SofficeUnavailableError を投げる', async () => {
    const dir = mkdtempSync(join(tmpdir(), 'office-preview-pptx-'));
    const pptxPath = join(dir, 'deck.pptx');
    writeFileSync(pptxPath, 'dummy'); // soffice は起動前に ENOENT になるので中身は不問
    const prev = process.env.SOFFICE_BIN;
    // SOFFICE_BIN は呼び出し時に解決されるので、存在しないバイナリを指せば soffice の
    // 導入有無に関係なく ENOENT → SofficeUnavailableError を確定的に再現できる。
    process.env.SOFFICE_BIN = join(dir, 'no-such-soffice-binary');
    try {
      await expect(getPresentationPreview(pptxPath)).rejects.toBeInstanceOf(SofficeUnavailableError);
    } finally {
      if (prev === undefined) delete process.env.SOFFICE_BIN;
      else process.env.SOFFICE_BIN = prev;
      rmSync(dir, { recursive: true, force: true });
    }
  });
});

describe('getDocumentPreview', () => {
  it('soffice が無い環境では SofficeUnavailableError を投げる', async () => {
    const dir = mkdtempSync(join(tmpdir(), 'office-preview-docx-'));
    const docxPath = join(dir, 'report.docx');
    writeFileSync(docxPath, 'dummy'); // soffice は起動前に ENOENT になるので中身は不問
    const prev = process.env.SOFFICE_BIN;
    // pptx と同じ経路 (soffice → PDF → PNG) を通るため、存在しないバイナリを指せば
    // docx でも確定的に SofficeUnavailableError を再現できる。
    process.env.SOFFICE_BIN = join(dir, 'no-such-soffice-binary');
    try {
      await expect(getDocumentPreview(docxPath)).rejects.toBeInstanceOf(SofficeUnavailableError);
    } finally {
      if (prev === undefined) delete process.env.SOFFICE_BIN;
      else process.env.SOFFICE_BIN = prev;
      rmSync(dir, { recursive: true, force: true });
    }
  });
});
