'use strict';

const { JPDecoder } = require('../lib/jp-decoder');
const { PassThrough, Transform } = require('stream');
const encodingJapanese = require('encoding-japanese');

// Helper to collect stream output
const collectStream = stream =>
    new Promise((resolve, reject) => {
        const chunks = [];
        stream.on('data', chunk => chunks.push(chunk));
        stream.on('end', () => resolve(Buffer.concat(chunks)));
        stream.on('error', reject);
    });

// ============================================
// Constructor tests
// ============================================

module.exports['JPDecoder: constructor sets charset'] = test => {
    let decoder = new JPDecoder('iso-2022-jp');
    test.equal(decoder.charset, 'iso-2022-jp');
    test.deepEqual(decoder.chunks, []);
    test.equal(decoder.chunklen, 0);
    test.done();
};

module.exports['JPDecoder: is a Transform stream'] = test => {
    let decoder = new JPDecoder('iso-2022-jp');
    test.ok(decoder instanceof Transform);
    test.ok(typeof decoder.pipe === 'function');
    test.ok(typeof decoder.write === 'function');
    test.done();
};

// ============================================
// _transform tests
// ============================================

module.exports['JPDecoder: _transform accumulates buffer chunks'] = test => {
    let decoder = new JPDecoder('iso-2022-jp');
    let chunk1 = Buffer.from('hello');
    let chunk2 = Buffer.from('world');

    decoder._transform(chunk1, 'buffer', () => {
        test.equal(decoder.chunks.length, 1);
        test.equal(decoder.chunklen, 5);

        decoder._transform(chunk2, 'buffer', () => {
            test.equal(decoder.chunks.length, 2);
            test.equal(decoder.chunklen, 10);
            test.done();
        });
    });
};

module.exports['JPDecoder: _transform converts string to buffer'] = test => {
    let decoder = new JPDecoder('iso-2022-jp');
    let stringChunk = 'hello';

    decoder._transform(stringChunk, 'utf8', () => {
        test.equal(decoder.chunks.length, 1);
        test.ok(Buffer.isBuffer(decoder.chunks[0]));
        test.equal(decoder.chunks[0].toString(), 'hello');
        test.done();
    });
};

// ============================================
// _flush tests
// ============================================

module.exports['JPDecoder: _flush outputs accumulated data'] = async test => {
    let decoder = new JPDecoder('utf-8');
    let output = collectStream(decoder);

    decoder.write(Buffer.from('hello '));
    decoder.write(Buffer.from('world'));
    decoder.end();

    let result = await output;
    test.equal(result.toString(), 'hello world');
    test.done();
};

module.exports['JPDecoder: _flush converts ISO-2022-JP to Unicode'] = async test => {
    let decoder = new JPDecoder('iso-2022-jp');
    let output = collectStream(decoder);

    // ISO-2022-JP encoded Japanese text for "nihongo" (Japanese)
    // ESC $ B sequence switches to JIS X 0208, ESC ( B switches back to ASCII
    let iso2022jp = Buffer.from([
        0x1b,
        0x24,
        0x42, // ESC $ B - switch to JIS X 0208
        0x46,
        0x7c, // ni
        0x4b,
        0x5c, // hon
        0x38,
        0x6c, // go
        0x1b,
        0x28,
        0x42 // ESC ( B - switch back to ASCII
    ]);

    decoder.write(iso2022jp);
    decoder.end();

    let result = await output;
    test.ok(result.length > 0);
    test.done();
};

module.exports['JPDecoder: _flush handles conversion errors gracefully'] = async test => {
    // Use an invalid/unknown charset to trigger error path
    let decoder = new JPDecoder('invalid-charset-xyz');
    let output = collectStream(decoder);

    let input = Buffer.from('test data');
    decoder.write(input);
    decoder.end();

    let result = await output;
    // On error, should return original input
    test.equal(result.toString(), 'test data');
    test.done();
};

module.exports['JPDecoder: _flush falls back to raw input when convert throws'] = async test => {
    // encoding-japanese rarely throws on its own, so force it: the module object
    // is shared by reference with the copy held inside jp-decoder.js, so patching
    // .convert here affects the decoder's call too.
    let originalConvert = encodingJapanese.convert;
    encodingJapanese.convert = () => {
        throw new Error('forced conversion failure');
    };

    try {
        let decoder = new JPDecoder('iso-2022-jp');
        let output = collectStream(decoder);

        let input = Buffer.from('raw bytes preserved');
        decoder.write(input);
        decoder.end();

        let result = await output;
        // On a thrown error the catch block pushes the original, untouched input
        test.equal(result.toString(), 'raw bytes preserved');
    } finally {
        encodingJapanese.convert = originalConvert;
    }
    test.done();
};

module.exports['JPDecoder: _flush converts non-string convert output to buffer'] = async test => {
    // When convert returns a non-string (e.g. an array of code units), it must be
    // pushed as-is without the Buffer.from(string) branch.
    let originalConvert = encodingJapanese.convert;
    encodingJapanese.convert = () => Buffer.from('converted buffer');

    try {
        let decoder = new JPDecoder('iso-2022-jp');
        let output = collectStream(decoder);

        decoder.write(Buffer.from('whatever'));
        decoder.end();

        let result = await output;
        test.equal(result.toString(), 'converted buffer');
    } finally {
        encodingJapanese.convert = originalConvert;
    }
    test.done();
};

module.exports['JPDecoder: _flush handles empty input'] = async test => {
    let decoder = new JPDecoder('iso-2022-jp');
    let output = collectStream(decoder);

    decoder.end();

    let result = await output;
    test.equal(result.length, 0);
    test.done();
};

// ============================================
// Integration tests
// ============================================

module.exports['JPDecoder: works with pipe'] = async test => {
    let source = new PassThrough();
    let decoder = new JPDecoder('utf-8');
    let output = collectStream(source.pipe(decoder));

    source.write('hello ');
    source.write('world');
    source.end();

    let result = await output;
    test.equal(result.toString(), 'hello world');
    test.done();
};

module.exports['JPDecoder: handles multiple small chunks'] = async test => {
    let decoder = new JPDecoder('utf-8');
    let output = collectStream(decoder);

    // Write character by character
    'hello'.split('').forEach(char => decoder.write(char));
    decoder.end();

    let result = await output;
    test.equal(result.toString(), 'hello');
    test.done();
};

module.exports['JPDecoder: handles Shift_JIS charset'] = async test => {
    let decoder = new JPDecoder('shift_jis');
    let output = collectStream(decoder);

    // Shift_JIS encoded "test" in katakana (tesuto)
    let shiftJis = Buffer.from([0x83, 0x65, 0x83, 0x58, 0x83, 0x67]);

    decoder.write(shiftJis);
    decoder.end();

    let result = await output;
    test.ok(result.length > 0);
    test.done();
};

module.exports['JPDecoder: handles EUC-JP charset'] = async test => {
    let decoder = new JPDecoder('euc-jp');
    let output = collectStream(decoder);

    // EUC-JP encoded Japanese character
    let eucJp = Buffer.from([0xc6, 0xfc, 0xcb, 0xdc]); // nihon

    decoder.write(eucJp);
    decoder.end();

    let result = await output;
    test.ok(result.length > 0);
    test.done();
};
