import { fail } from 'assert';
import { promises as fs } from 'fs';

import ExcelJS from 'exceljs';
import { describe, expect, test } from 'vitest';

import { METADATA_FILE_KIND, PLAIN_SEGMENT_KIND } from './fileProcessing';

async function buildWorkbookFile(extraSheetNames: string[]): Promise<File> {
    const workbook = new ExcelJS.Workbook();
    const dataSheet = workbook.addWorksheet('Data');
    dataSheet.addRow(['submissionId', 'country']);
    dataSheet.addRow(['sample1', 'Germany']);
    for (const sheetName of extraSheetNames) {
        workbook.addWorksheet(sheetName).addRow(['ignored']);
    }
    const buffer = await workbook.xlsx.writeBuffer();
    return new File([buffer], 'template.xlsx');
}

async function loadTestFile(fileName: string): Promise<File> {
    const path = `${import.meta.dirname}/test_files/${fileName}`;
    const contents = await fs.readFile(path);
    return new File([new Uint8Array(contents)], fileName);
}

describe('fileProcessing', () => {
    test('loading TSV', async () => {
        const file = await loadTestFile('testfile.tsv');
        const processingResult = await METADATA_FILE_KIND.processRawFile(file);

        expect(processingResult.isOk());
        const processedFile = processingResult._unsafeUnwrap();

        expect(processedFile.warnings().length).toBe(0);
        expect(processedFile.handle()).toBe(file);
    });

    test.each([
        ['testfile.tsv.zip', 0],
        ['testfile.tsv.gz', 0],
        ['testfile.tsv.zst', 0],
        ['testfile_different_formats.xls', 0],
        ['testfile_different_formats.xlsx', 0],
        ['testfile_different_formats.xlsx.gz', 0],
        ['testfile_different_formats.xlsx.zip', 0],
        ['testfile_different_formats.xlsx.zst', 0],
        ['testfile_with_second_sheet.xls', 1],
        ['testfile_with_second_sheet.xlsx', 1],
    ])(
        'should load %s file correctly',
        async (filename, warningsCount) => {
            const tsvFileContent = await loadTestFile('testfile.tsv')
                .then((file) => file.text())
                .then((text) => text.replace(/[\r]+/g, ''));

            const file = await loadTestFile(filename);
            const processingResult = await METADATA_FILE_KIND.processRawFile(file);

            expect(processingResult.isOk()).toBe(true);
            const processedFile = processingResult._unsafeUnwrap();

            expect(processedFile.warnings().length).toBe(warningsCount);
            expect(await processedFile.text().then((text) => text.replace(/[\r]+/g, ''))).toEqual(tsvFileContent);
        },
        10000,
    );

    test('LZMA compressed excel file cannot be processed', async () => {
        const file = await loadTestFile('testfile_different_formats.xlsx.xz');
        const processingResult = await METADATA_FILE_KIND.processRawFile(file);

        expect(processingResult.isErr()).toBeTruthy();
    });

    test('Unsupported metadata extension results in error with message', async () => {
        const file = new File(['dummy'], 'unsupported.txt');
        const processingResult = await METADATA_FILE_KIND.processRawFile(file);

        expect(processingResult.isErr()).toBeTruthy();
        const message = processingResult._unsafeUnwrapErr().message;
        expect(message).toContain('Unsupported file extension');
    });

    test('Plain segment file content is extracted correctly', async () => {
        const dummyFile = new File(['>fooid description\nACTG\n\nACTG\nACTG\n'], 'example.fasta', {
            type: 'text/plain',
        });
        const result = await PLAIN_SEGMENT_KIND.processRawFile(dummyFile);
        if (result.isErr()) {
            fail(`result was error: ${result._unsafeUnwrapErr().message}`);
        }
        const processedFile = result._unsafeUnwrap();
        const processedText = await processedFile.text();
        expect(processedText).toBe('ACTGACTGACTG');
        expect(processedFile.fastaHeader()).toBe('fooid description');
    });

    test('template reference sheets (Guidance, _lists) do not trigger a multi-sheet warning', async () => {
        const file = await buildWorkbookFile(['Guidance', '_lists']);
        const processingResult = await METADATA_FILE_KIND.processRawFile(file);

        expect(processingResult.isOk()).toBe(true);
        expect(processingResult._unsafeUnwrap().warnings()).toHaveLength(0);
    });

    test('an unexpected extra sheet still triggers a multi-sheet warning', async () => {
        const file = await buildWorkbookFile(['Guidance', 'My other data']);
        const processingResult = await METADATA_FILE_KIND.processRawFile(file);

        expect(processingResult.isOk()).toBe(true);
        expect(processingResult._unsafeUnwrap().warnings()).toHaveLength(1);
    });

    test('parses the Data sheet by name even when it is not the first sheet', async () => {
        // Reference sheet dragged in front of Data — the Data sheet must still be the one parsed.
        const workbook = new ExcelJS.Workbook();
        workbook.addWorksheet('Guidance').addRow(['Field name', 'Display name']);
        const data = workbook.addWorksheet('Data');
        data.addRow(['submissionId', 'country']);
        data.addRow(['sample1', 'Germany']);
        const buffer = await workbook.xlsx.writeBuffer();
        const file = new File([buffer], 'template.xlsx');

        const processingResult = await METADATA_FILE_KIND.processRawFile(file);
        expect(processingResult.isOk()).toBe(true);
        const processedFile = processingResult._unsafeUnwrap();

        expect(processedFile.warnings()).toHaveLength(0);
        const text = await processedFile.text();
        expect(text.split('\n')[0]).toContain('submissionId'); // parsed Data...
        expect(text).not.toContain('Field name'); // ...not the Guidance reference sheet
    });
});
