'use strict';

const { LimitedPassthrough } = require('../lib/limited-passthrough');
const { PassThrough, Transform } = require('stream');

// 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['LimitedPassthrough: constructor with options'] = test => {
    let stream = new LimitedPassthrough({ maxBytes: 100 });
    test.equal(stream.maxBytes, 100);
    test.equal(stream.processed, 0);
    test.equal(stream.limited, false);
    test.done();
};

module.exports['LimitedPassthrough: constructor with no options'] = test => {
    let stream = new LimitedPassthrough();
    test.equal(stream.maxBytes, Infinity);
    test.equal(stream.processed, 0);
    test.equal(stream.limited, false);
    test.done();
};

module.exports['LimitedPassthrough: constructor with null options'] = test => {
    let stream = new LimitedPassthrough(null);
    test.equal(stream.maxBytes, Infinity);
    test.done();
};

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

// ============================================
// _transform tests - no limit
// ============================================

module.exports['LimitedPassthrough: passes all data when no limit'] = async test => {
    let stream = new LimitedPassthrough();
    let output = collectStream(stream);

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

    let result = await output;
    test.equal(result.toString(), 'hello world');
    test.equal(stream.processed, 11);
    test.equal(stream.limited, false);
    test.done();
};

// ============================================
// _transform tests - with limit
// ============================================

module.exports['LimitedPassthrough: limits output to maxBytes'] = async test => {
    let stream = new LimitedPassthrough({ maxBytes: 5 });
    let output = collectStream(stream);

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

    let result = await output;
    test.equal(result.toString(), 'hello');
    test.equal(stream.processed, 5);
    test.equal(stream.limited, true);
    test.done();
};

module.exports['LimitedPassthrough: limits across multiple chunks'] = async test => {
    let stream = new LimitedPassthrough({ maxBytes: 8 });
    let output = collectStream(stream);

    stream.write(Buffer.from('hello ')); // 6 bytes
    stream.write(Buffer.from('world')); // 5 bytes, only 2 should pass
    stream.end();

    let result = await output;
    test.equal(result.toString(), 'hello wo');
    test.equal(stream.processed, 8);
    test.equal(stream.limited, true);
    test.done();
};

module.exports['LimitedPassthrough: drops data after limit reached'] = async test => {
    let stream = new LimitedPassthrough({ maxBytes: 5 });
    let output = collectStream(stream);

    stream.write(Buffer.from('hello')); // exactly 5 bytes
    stream.write(Buffer.from(' world')); // should be dropped
    stream.write(Buffer.from('!')); // should be dropped
    stream.end();

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

module.exports['LimitedPassthrough: handles exact boundary'] = async test => {
    let stream = new LimitedPassthrough({ maxBytes: 5 });
    let output = collectStream(stream);

    stream.write(Buffer.from('hello')); // exactly 5 bytes
    stream.end();

    let result = await output;
    test.equal(result.toString(), 'hello');
    test.equal(stream.processed, 5);
    test.equal(stream.limited, true);
    test.done();
};

module.exports['LimitedPassthrough: zero maxBytes treated as Infinity'] = async test => {
    // Note: maxBytes: 0 is falsy, so constructor uses Infinity instead
    let stream = new LimitedPassthrough({ maxBytes: 0 });
    test.equal(stream.maxBytes, Infinity);

    let output = collectStream(stream);
    stream.write(Buffer.from('hello'));
    stream.end();

    let result = await output;
    // All data passes through since 0 is treated as Infinity
    test.equal(result.toString(), 'hello');
    test.done();
};

module.exports['LimitedPassthrough: drops chunk when no remaining budget but not yet limited'] = async test => {
    // Force the remainingBytes<1 guard (distinct from the `this.limited` fast path):
    // override maxBytes to 0 after construction so the very first chunk hits it
    // while `limited` is still false.
    let stream = new LimitedPassthrough();
    stream.maxBytes = 0;
    let output = collectStream(stream);

    stream.write(Buffer.from('dropped'));
    stream.end();

    let result = await output;
    test.equal(result.length, 0, 'chunk dropped when no budget remains');
    test.equal(stream.limited, false, 'limited flag never set via this path');
    test.done();
};

module.exports['LimitedPassthrough: handles limit of 1 byte'] = async test => {
    let stream = new LimitedPassthrough({ maxBytes: 1 });
    let output = collectStream(stream);

    stream.write(Buffer.from('hello'));
    stream.end();

    let result = await output;
    test.equal(result.toString(), 'h');
    test.equal(stream.processed, 1);
    test.equal(stream.limited, true);
    test.done();
};

// ============================================
// Edge cases
// ============================================

module.exports['LimitedPassthrough: handles empty writes'] = async test => {
    let stream = new LimitedPassthrough({ maxBytes: 10 });
    let output = collectStream(stream);

    stream.write(Buffer.from(''));
    stream.write(Buffer.from('hello'));
    stream.write(Buffer.from(''));
    stream.end();

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

module.exports['LimitedPassthrough: handles no writes'] = async test => {
    let stream = new LimitedPassthrough({ maxBytes: 10 });
    let output = collectStream(stream);

    stream.end();

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

module.exports['LimitedPassthrough: tracks processed bytes correctly'] = async test => {
    let stream = new LimitedPassthrough({ maxBytes: 100 });
    let output = collectStream(stream);

    stream.write(Buffer.from('12345')); // 5 bytes
    test.equal(stream.processed, 5);

    stream.write(Buffer.from('67890')); // 5 more bytes
    test.equal(stream.processed, 10);

    stream.end();

    await output;
    test.equal(stream.processed, 10);
    test.done();
};

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

module.exports['LimitedPassthrough: works with pipe'] = async test => {
    let source = new PassThrough();
    let limiter = new LimitedPassthrough({ maxBytes: 10 });
    let output = collectStream(source.pipe(limiter));

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

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

module.exports['LimitedPassthrough: handles large data'] = async test => {
    let stream = new LimitedPassthrough({ maxBytes: 1000 });
    let output = collectStream(stream);

    // Write 100 bytes at a time
    for (let i = 0; i < 20; i++) {
        stream.write(Buffer.alloc(100, 'x'));
    }
    stream.end();

    let result = await output;
    test.equal(result.length, 1000);
    test.equal(stream.limited, true);
    test.done();
};

module.exports['LimitedPassthrough: single byte writes'] = async test => {
    let stream = new LimitedPassthrough({ maxBytes: 3 });
    let output = collectStream(stream);

    stream.write(Buffer.from('a'));
    stream.write(Buffer.from('b'));
    stream.write(Buffer.from('c'));
    stream.write(Buffer.from('d')); // should be dropped
    stream.write(Buffer.from('e')); // should be dropped
    stream.end();

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