'use strict';

const { ImapFlow } = require('../lib/imap-flow');
const { EventEmitter } = require('events');
const net = require('net');

// Edge Cases Tests

module.exports['Connection Edge: Socket error during connection'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Simulate socket error
    let errorEmitted = false;
    client.on('error', err => {
        errorEmitted = true;
        test.ok(err);
        test.equal(err._connId, client.id);
    });

    // Trigger error through emitError
    let testError = new Error('Socket connection failed');
    testError.code = 'ECONNREFUSED';
    client.emitError(testError);

    test.ok(errorEmitted, 'Error event should be emitted');
    test.done();
};

module.exports['Connection Edge: Write after socket destroyed'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Simulate destroyed socket
    client.socket = { destroyed: true };

    test.throws(
        () => {
            client.write('TEST');
        },
        /Socket is already closed/,
        'Should throw when writing to destroyed socket'
    );

    test.done();
};

module.exports['Connection Edge: Write after logout'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Create a mock socket
    client.socket = { destroyed: false };
    client.state = client.states.LOGOUT;

    test.throws(
        () => {
            client.write('TEST');
        },
        /Can not send data after logged out/,
        'Should throw when writing after logout'
    );

    test.done();
};

module.exports['Connection Edge: Stats reset functionality'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Set some initial values
    client.writeBytesCounter = 100;
    client.streamer.readBytesCounter = 200;

    let stats = client.stats(false);
    test.equal(stats.sent, 100);
    test.equal(stats.received, 200);

    // Reset stats
    stats = client.stats(true);
    test.equal(stats.sent, 100, 'Should return old value before reset');
    test.equal(stats.received, 200, 'Should return old value before reset');

    // Check if reset worked
    test.equal(client.writeBytesCounter, 0, 'Write counter should be reset');
    test.equal(client.streamer.readBytesCounter, 0, 'Read counter should be reset');

    test.done();
};

module.exports['Connection Edge: Multiple error handlers'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    let errorCount = 0;

    // Add multiple error handlers
    client.on('error', () => errorCount++);
    client.on('error', () => errorCount++);
    client.on('error', () => errorCount++);

    // Emit an error
    client.emitError(new Error('Test error'));

    test.equal(errorCount, 3, 'All error handlers should be called');
    test.done();
};

module.exports['Connection Edge: Connection with valid empty auth'] = test => {
    // ImapFlow actually allows creating client without throwing
    // The error would occur during connection
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test' }
    });

    test.ok(client, 'Client should be created even with partial auth');
    test.equal(client.host, 'imap.example.com');
    test.done();
};

module.exports['Connection Edge: Connection with default values'] = test => {
    // ImapFlow sets default values for missing options
    let client = new ImapFlow({
        host: 'imap.example.com',
        auth: { user: 'test', pass: 'test' }
    });

    test.ok(client, 'Client should be created with defaults');
    test.equal(client.port, 110, 'Should use default port');
    test.equal(client.secureConnection, false, 'Should default to non-secure');
    test.done();
};

module.exports['Connection Edge: Port number handling'] = test => {
    // ImapFlow sets default port based on secure flag
    let client1 = new ImapFlow({
        host: 'imap.example.com',
        secure: false,
        auth: { user: 'test', pass: 'test' }
    });
    test.equal(client1.port, 110, 'Default non-secure port');

    let client2 = new ImapFlow({
        host: 'imap.example.com',
        port: 65535,
        auth: { user: 'test', pass: 'test' }
    });
    test.equal(client2.port, 65535, 'Custom port accepted');

    test.done();
};

module.exports['Connection Edge: STARTTLS misconfiguration'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 143,
        secure: true,
        doSTARTTLS: true,
        auth: { user: 'test', pass: 'test' }
    });

    // Test that upgradeToSTARTTLS throws on misconfiguration
    client.upgradeToSTARTTLS().catch(err => {
        test.ok(err);
        test.ok(err.message.includes('Misconfiguration'), 'Should detect STARTTLS misconfiguration');
        test.done();
    });
};

module.exports['Connection Edge: Socket timeout handling'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Create a mock socket
    client.socket = new EventEmitter();
    client.socket.destroyed = false;
    client.socket.destroy = () => {};
    client.writeSocket = client.socket;
    client.writeSocket.destroy = () => {};

    let errorEmitted = false;
    client.on('error', err => {
        errorEmitted = true;
        test.equal(err.code, 'ETIMEOUT');
    });

    // Set up socket handlers
    client.setSocketHandlers();

    // Simulate timeout on non-idle connection
    client.idling = false;
    client.socket.emit('timeout');

    test.ok(errorEmitted, 'Timeout error should be emitted');
    test.done();
};

module.exports['Connection Edge: Socket timeout during IDLE'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Create mock socket and methods
    client.socket = new EventEmitter();
    client.socket.destroyed = false;
    client.writeSocket = client.socket;
    client.usable = true;
    client.idling = true;

    // Mock run and idle methods
    let noopCalled = false;
    let idleCalled = false;

    client.run = async command => {
        if (command === 'NOOP') {
            noopCalled = true;
            return Promise.resolve();
        }
    };

    client.idle = async () => {
        idleCalled = true;
        return Promise.resolve();
    };

    // Set up socket handlers
    client.setSocketHandlers();

    // Simulate timeout during IDLE
    client.socket.emit('timeout');

    // Give async operations time to complete
    setTimeout(() => {
        test.ok(noopCalled, 'NOOP should be called to recover from IDLE timeout');
        test.ok(idleCalled, 'Should return to IDLE after NOOP');
        test.done();
    }, 100);
};

module.exports['Connection Edge: Clear socket handlers'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Create a mock socket
    client.socket = new EventEmitter();
    client.writeSocket = client.socket;

    // Set handlers
    client.setSocketHandlers();

    // Verify handlers are set
    test.equal(client.socket.listenerCount('error'), 1);
    test.equal(client.socket.listenerCount('close'), 1);
    test.equal(client.socket.listenerCount('end'), 1);
    test.equal(client.socket.listenerCount('timeout'), 1);

    // Clear handlers
    client.clearSocketHandlers();

    // Verify handlers are removed
    test.equal(client.socket.listenerCount('error'), 0);
    test.equal(client.socket.listenerCount('close'), 0);
    test.equal(client.socket.listenerCount('end'), 0);
    test.equal(client.socket.listenerCount('timeout'), 0);

    test.done();
};

module.exports['Connection Edge: Write with null socket'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Set socket to null
    client.socket = null;

    test.throws(
        () => {
            client.write('TEST');
        },
        /Socket is already closed/,
        'Should throw when socket is null'
    );

    test.done();
};

module.exports['Connection Edge: Compression error handling'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Create mock socket and streams
    client.socket = new EventEmitter();
    client.socket.pipe = () => client.socket;
    client.socket.unpipe = () => {};
    client.streamer = new EventEmitter();

    // Mock run method to simulate successful COMPRESS negotiation
    client.run = async command => {
        if (command === 'COMPRESS') {
            return true;
        }
    };

    let errorEmitted = false;
    client.streamer.on('error', err => {
        errorEmitted = true;
        test.ok(err);
    });

    // Call compress
    client.compress().then(() => {
        // Simulate compression error
        if (client._inflate) {
            client._inflate.emit('error', new Error('Compression failed'));
        }

        test.ok(errorEmitted, 'Compression error should be propagated');
        test.done();
    });
};

module.exports['Connection Edge: Authentication state after logout'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Set initial authenticated state
    client.state = client.states.AUTHENTICATED;
    client.authenticated = true;

    // Simulate logout
    client.state = client.states.LOGOUT;

    // Try to authenticate
    client.authenticate().catch(err => {
        test.ok(err);
        test.equal(err.message, 'Already logged out');
        test.done();
    });
};

module.exports['Connection Edge: Throttling detection'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Create mock request
    let errorReceived = null;
    let request = {
        tag: 'A001',
        command: 'FETCH',
        resolve: () => {},
        reject: err => {
            errorReceived = err;
        }
    };

    client.requestTagMap = new Map();
    client.requestTagMap.set('A001', request);

    // Simulate throttling response
    let parsed = {
        tag: 'A001',
        command: 'BAD',
        attributes: [
            {
                type: 'TEXT',
                value: 'Request is throttled. Suggested Backoff Time: 5000 milliseconds'
            }
        ]
    };

    // Mock streamer with proper async iterator
    client.streamer = new EventEmitter();
    client.streamer.read = () => null;
    client.streamer.iterate = async function* () {
        yield {
            next: () => {},
            parsed
        };
    };

    // Process reader
    client
        .reader()
        .then(() => {
            if (errorReceived) {
                test.ok(errorReceived, 'Should receive throttle error');
                test.equal(errorReceived.code, 'ETHROTTLE');
                test.equal(errorReceived.throttleReset, 5000);
            }
            test.done();
        })
        .catch(() => {
            // Reader might fail in test environment
            test.done();
        });
};

module.exports['Connection Edge: Binary data in write'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Mock socket
    let writtenData = null;
    client.socket = { destroyed: false };
    client.writeSocket = {
        destroyed: false,
        write: data => {
            writtenData = data;
        }
    };

    // Test writing Buffer
    let testBuffer = Buffer.from('TEST');
    client.write(testBuffer);

    test.ok(Buffer.isBuffer(writtenData));
    test.ok(writtenData.includes(testBuffer));
    test.ok(writtenData.includes(Buffer.from('\r\n')));

    // Test writing string
    client.commandParts = [];
    client.write('STRING_TEST');

    test.ok(Buffer.isBuffer(writtenData));
    test.ok(writtenData.includes(Buffer.from('STRING_TEST\r\n')));

    test.done();
};

module.exports['Connection Edge: Invalid write data type'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Mock socket
    client.socket = { destroyed: false };
    client.writeSocket = { destroyed: false, write: () => {} };

    // Test writing invalid data type
    let result = client.write(12345);
    test.equal(result, false, 'Should return false for invalid data type');

    result = client.write({ test: 'object' });
    test.equal(result, false, 'Should return false for object');

    test.done();
};

module.exports['Connection Edge: Concurrent connections'] = test => {
    let clients = [];

    // Create multiple clients
    for (let i = 0; i < 5; i++) {
        let client = new ImapFlow({
            host: `imap${i}.example.com`,
            port: 993,
            auth: { user: `user${i}`, pass: `pass${i}` }
        });
        clients.push(client);
    }

    // Verify each client has unique ID
    let ids = clients.map(c => c.id);
    let uniqueIds = [...new Set(ids)];
    test.equal(uniqueIds.length, clients.length, 'All client IDs should be unique');

    // Verify each client has independent state
    clients[0].state = clients[0].states.AUTHENTICATED;
    clients[1].state = clients[1].states.SELECTED;

    test.equal(clients[0].state, clients[0].states.AUTHENTICATED);
    test.equal(clients[1].state, clients[1].states.SELECTED);
    test.equal(clients[2].state, clients[2].states.NOT_AUTHENTICATED);

    test.done();
};

module.exports['Connection Edge: Destroyed write socket'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Mock sockets
    client.socket = { destroyed: false };
    client.writeSocket = { destroyed: true };

    // Mock close method
    let closeCalled = false;
    client.close = () => {
        closeCalled = true;
    };

    // Attempt to write
    client.write('TEST');

    test.ok(closeCalled, 'Should call close when write socket is destroyed');
    test.done();
};

module.exports['Connection Edge: Command after logout'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Set logout state
    client.state = client.states.LOGOUT;

    // Create a request
    let errorReceived = null;
    let request = {
        tag: 'A001',
        reject: err => {
            errorReceived = err;
        }
    };

    client.requestTagMap = new Map();
    client.requestTagMap.set('A001', request);

    // Try to send command
    client.send({ tag: 'A001', command: 'NOOP' }).then(() => {
        test.ok(errorReceived, 'Should reject command after logout');
        test.equal(errorReceived.code, 'NoConnection');
        test.done();
    });
};

module.exports['Connection Edge: Race condition in mailbox lock'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Initialize locks array
    client.locks = [];

    // Simulate concurrent lock requests
    let lockPromises = [];
    for (let i = 0; i < 3; i++) {
        lockPromises.push(
            new Promise(resolve => {
                client.locks.push({
                    path: 'INBOX',
                    resolve,
                    promise: new Promise(() => {})
                });
            })
        );
    }

    test.equal(client.locks.length, 3, 'Should queue multiple lock requests');

    // Process locks sequentially
    client.locks.forEach((lock, index) => {
        lock.resolve({ path: 'INBOX', index });
    });

    Promise.all(lockPromises).then(results => {
        test.equal(results.length, 3, 'All lock requests should be resolved');
        test.done();
    });
};

module.exports['Connection Edge: Capability update after STARTTLS'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 143,
        auth: { user: 'test', pass: 'test' }
    });

    // Mock capabilities
    client.capabilities = new Map();
    client.capabilities.set('STARTTLS', true);

    // Mock run method
    client.run = async command => {
        if (command === 'STARTTLS') {
            client.expectCapabilityUpdate = true;
            return true;
        }
        if (command === 'CAPABILITY') {
            return true;
        }
    };

    // Mock socket upgrade
    client.socket = new EventEmitter();
    client.socket.unpipe = () => {};
    client.streamer = new EventEmitter();

    // Override the upgradeToSTARTTLS to test capability update
    client
        .upgradeToSTARTTLS()
        .then(result => {
            test.ok(result, 'Should successfully upgrade to TLS');
            test.ok(client.expectCapabilityUpdate, 'Should expect capability update');
            test.done();
        })
        .catch(() => {
            // Expected for this mock setup
            test.done();
        });
};

module.exports['Connection Edge: Event handlers attached before piping'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Track the order of operations during the connection flow
    let pipeCalledBeforeHandlers = false;
    let eventHandlersAttached = false;
    let pipeWasCalled = false;

    // Override setEventHandlers to track when it's called
    let originalSetEventHandlers = client.setEventHandlers.bind(client);
    client.setEventHandlers = function () {
        eventHandlersAttached = true;
        return originalSetEventHandlers();
    };

    // Create a mock socket with pipe tracking
    let mockSocket = new EventEmitter();
    mockSocket.setKeepAlive = () => {};
    mockSocket.setTimeout = () => {};
    mockSocket.remotePort = 993;
    mockSocket.remoteAddress = '127.0.0.1';
    mockSocket.localAddress = '127.0.0.1';
    mockSocket.localPort = 12345;
    mockSocket.destroyed = false;
    mockSocket.pipe = function (dest) {
        pipeWasCalled = true;
        if (!eventHandlersAttached) {
            pipeCalledBeforeHandlers = true;
        }
        // Mock pipe behavior - just return the destination
        return dest;
    };

    // Assign mock socket
    client.socket = mockSocket;
    client.writeSocket = mockSocket;

    // Simulate the onConnect flow that happens in the actual code
    client.setSocketHandlers();
    client.setEventHandlers();
    client.socket.pipe(client.streamer);

    test.ok(eventHandlersAttached, 'Event handlers should be attached');
    test.ok(pipeWasCalled, 'Socket pipe should be called');
    test.ok(!pipeCalledBeforeHandlers, 'Event handlers should be attached before piping socket to streamer');

    test.done();
};

module.exports['Connection Edge: Pending locks rejected on close'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Initialize locks array with pending locks
    let rejectedErrors = [];
    client.locks = [
        {
            path: 'INBOX',
            lockId: 'lock1',
            resolve: () => {},
            reject: err => rejectedErrors.push(err)
        },
        {
            path: 'Sent',
            lockId: 'lock2',
            resolve: () => {},
            reject: err => rejectedErrors.push(err)
        }
    ];

    // Mock socket to allow close() to run
    client.socket = { destroyed: false, destroy: () => {} };
    client.usable = true;

    // Call close
    client.close();

    // Rejections happen via setImmediate, so check after next tick
    setImmediate(() => {
        test.equal(rejectedErrors.length, 2, 'All pending locks should be rejected');
        test.equal(rejectedErrors[0].code, 'NoConnection', 'Error should have NoConnection code');
        test.equal(rejectedErrors[1].code, 'NoConnection', 'Error should have NoConnection code');
        test.equal(client.locks.length, 0, 'Locks array should be cleared');
        test.done();
    });
};

module.exports['Connection Edge: Lock rejection includes byeReason'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Set byeReason before close
    client.byeReason = 'Server shutting down';

    let rejectedError = null;
    client.locks = [
        {
            path: 'INBOX',
            lockId: 'lock1',
            resolve: () => {},
            reject: err => {
                rejectedError = err;
            }
        }
    ];

    client.socket = { destroyed: false, destroy: () => {} };
    client.usable = true;

    client.close();

    setImmediate(() => {
        test.ok(rejectedError, 'Lock should be rejected');
        test.equal(rejectedError.code, 'NoConnection');
        test.equal(rejectedError.reason, 'Server shutting down', 'byeReason should be included');
        test.done();
    });
};

module.exports['Connection Edge: currentLock cleared on close'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Simulate an active lock
    client.currentLock = {
        path: 'INBOX',
        lockId: 'active-lock',
        release: () => {}
    };
    client.locks = [];

    client.socket = { destroyed: false, destroy: () => {} };
    client.usable = true;

    client.close();

    test.equal(client.currentLock, false, 'currentLock should be cleared');
    test.done();
};

module.exports['Connection Edge: Lock rejection handles missing reject function'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Create lock with missing reject function (edge case)
    let validRejected = false;
    client.locks = [
        {
            path: 'INBOX',
            lockId: 'lock1',
            resolve: () => {}
            // No reject function
        },
        {
            path: 'Sent',
            lockId: 'lock2',
            resolve: () => {},
            reject: () => {
                validRejected = true;
            }
        }
    ];

    client.socket = { destroyed: false, destroy: () => {} };
    client.usable = true;

    // Should not throw
    test.doesNotThrow(() => {
        client.close();
    }, 'Should handle missing reject function gracefully');

    setImmediate(() => {
        test.ok(validRejected, 'Valid lock should still be rejected');
        test.equal(client.locks.length, 0, 'Locks array should be cleared');
        test.done();
    });
};

module.exports['Connection Edge: Close with empty locks array'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    client.locks = [];
    client.currentLock = false;
    client.socket = { destroyed: false, destroy: () => {} };
    client.usable = true;

    // Should not throw with empty locks
    test.doesNotThrow(() => {
        client.close();
    }, 'Should handle empty locks array');

    test.equal(client.currentLock, false);
    test.done();
};

module.exports['Connection Edge: Lock rejection happens synchronously during close'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    let rejectionTime = null;

    client.locks = [
        {
            path: 'INBOX',
            lockId: 'lock1',
            resolve: () => {},
            reject: () => {
                rejectionTime = Date.now();
            }
        }
    ];

    client.socket = { destroyed: false, destroy: () => {} };
    client.usable = true;

    client.close();

    // Rejection should have happened synchronously during close()
    test.ok(rejectionTime !== null, 'Rejection should happen synchronously during close()');
    test.done();
};

module.exports['Connection Edge: Pending requests and locks both rejected on close'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    let requestRejected = false;
    let lockRejected = false;

    // Add pending request - must be in requestQueue and requestTagMap
    let request = {
        tag: 'A001',
        reject: err => {
            requestRejected = true;
            test.equal(err.code, 'NoConnection');
        }
    };
    client.requestTagMap = new Map();
    client.requestTagMap.set('A001', request);
    client.requestQueue = [request];

    // Add pending lock
    client.locks = [
        {
            path: 'INBOX',
            lockId: 'lock1',
            resolve: () => {},
            reject: err => {
                lockRejected = true;
                test.equal(err.code, 'NoConnection');
            }
        }
    ];

    client.socket = { destroyed: false, destroy: () => {} };
    client.usable = true;

    client.close();

    // Rejections happen synchronously during close()
    test.ok(requestRejected, 'Pending request should be rejected');
    test.ok(lockRejected, 'Pending lock should be rejected');
    test.done();
};

module.exports['Connection Edge: exec throws NoConnection when in LOGOUT state'] = async test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Set state to LOGOUT
    client.state = client.states.LOGOUT;
    client.socket = { destroyed: false };

    try {
        await client.exec('NOOP', []);
        test.ok(false, 'Should have thrown');
    } catch (err) {
        test.equal(err.code, 'NoConnection');
        test.ok(err.message.includes('Connection not available'));
    }

    test.done();
};

module.exports['Connection Edge: exec throws NoConnection when isClosed is true'] = async test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Set isClosed flag
    client.isClosed = true;
    client.socket = { destroyed: false };

    try {
        await client.exec('NOOP', []);
        test.ok(false, 'Should have thrown');
    } catch (err) {
        test.equal(err.code, 'NoConnection');
        test.ok(err.message.includes('Connection not available'));
    }

    test.done();
};

module.exports['Connection Edge: getLogger emits log events when emitLogs is true'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' },
        emitLogs: true
    });

    let logEvents = [];
    client.on('log', entry => {
        logEvents.push(entry);
    });

    // Trigger logging through the logger
    client.log.info({ msg: 'Test message', extra: 'data' });

    test.ok(logEvents.length > 0, 'Log event should be emitted');
    test.equal(logEvents[0].level, 'info');
    test.equal(logEvents[0].msg, 'Test message');
    test.equal(logEvents[0].extra, 'data');
    test.ok(logEvents[0].cid, 'Should have connection id');
    test.ok(logEvents[0].t, 'Should have timestamp');
    test.done();
};

module.exports['Connection Edge: getLogger emits log with error object'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' },
        emitLogs: true
    });

    let logEvents = [];
    client.on('log', entry => {
        logEvents.push(entry);
    });

    // Trigger error logging
    let testError = new Error('Test error');
    testError.code = 'TEST_CODE';
    client.log.error({ msg: 'Error occurred', err: testError });

    test.ok(logEvents.length > 0, 'Log event should be emitted');
    test.equal(logEvents[0].level, 'error');
    test.ok(logEvents[0].err, 'Should have error object');
    test.ok(logEvents[0].err.stack, 'Error should have stack');
    test.equal(logEvents[0].err.code, 'TEST_CODE', 'Error should have code');
    test.done();
};

module.exports['Connection Edge: unbind removes socket listeners and returns sockets'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Create mock socket with unpipe method
    client.socket = new EventEmitter();
    client.socket.unpipe = () => {};
    client.writeSocket = client.socket;

    // Set up socket handlers first
    client.setSocketHandlers();

    // Verify handlers are set
    test.equal(client.socket.listenerCount('error'), 1);
    test.equal(client.socket.listenerCount('close'), 1);

    // Call unbind
    let result = client.unbind();

    // Verify handlers are removed
    test.equal(client.socket.listenerCount('error'), 0);
    test.equal(client.socket.listenerCount('close'), 0);
    test.equal(client.socket.listenerCount('end'), 0);
    test.equal(client.socket.listenerCount('timeout'), 0);

    // Verify return value
    test.ok(result.readSocket, 'Should return readSocket');
    test.ok(result.writeSocket, 'Should return writeSocket');
    test.equal(result.readSocket, client.socket);
    test.equal(result.writeSocket, client.socket);
    // Non-compression path: the raw socket is the same object as read/write.
    test.equal(result.socket, client.socket);

    test.done();
};

module.exports['Connection Edge: unbind with inflate stream'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Create mock socket
    client.socket = new EventEmitter();
    client.socket.unpipe = () => {};

    // Create a separate writeSocket (also needs to be EventEmitter for setSocketHandlers)
    client.writeSocket = new EventEmitter();
    client.writeSocket.customProp = 'writeSocket';

    // Create mock inflate stream
    client._inflate = new EventEmitter();
    client._inflate.unpipe = () => {};

    // Set up socket handlers
    client.setSocketHandlers();

    // Call unbind
    let result = client.unbind();

    // Verify return value uses inflate for read and writeSocket for write
    test.equal(result.readSocket, client._inflate);
    test.equal(result.writeSocket.customProp, 'writeSocket');

    test.done();
};

module.exports['Connection Edge: unbind survives post-handoff socket error (compression)'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Mock the compression topology: raw socket plus a separate PassThrough-like
    // writeSocket, an inflate read stream and a deflate stream. The writeSocket
    // and deflate error forwarders re-emit onto the raw socket, exactly like compress().
    client.socket = new EventEmitter();
    client.socket.unpipe = () => {};

    client.writeSocket = new EventEmitter();
    client.writeSocket.on('error', err => {
        if (client.socket) {
            client.socket.emit('error', err);
        }
    });

    client._inflate = new EventEmitter();
    client._inflate.unpipe = () => {};

    client._deflate = new EventEmitter();
    client._deflate.on('error', err => {
        if (client.socket) {
            client.socket.emit('error', err);
        }
    });

    client.setSocketHandlers();

    let result = client.unbind();

    // Explicit contract: raw socket is now exposed alongside read/write sockets.
    test.equal(result.socket, client.socket, 'Should expose the raw socket');
    test.equal(result.readSocket, client._inflate);
    test.equal(result.writeSocket, client.writeSocket);

    // ImapFlow's own _socketError must be detached from writeSocket; only the
    // forwarder closure remains.
    test.equal(client.writeSocket.listenerCount('error'), 1);

    // Emitting 'error' on a listener-less EventEmitter throws synchronously, so
    // each of these would crash the host before the fix. After unbind() the
    // benign listener on the orphaned raw socket must swallow them all.
    test.doesNotThrow(() => {
        client.socket.emit('error', Object.assign(new Error('upstream reset'), { code: 'ECONNRESET' }));
    }, 'Direct socket error after unbind must not throw');

    test.doesNotThrow(() => {
        client.writeSocket.emit('error', new Error('write failure'));
    }, 'writeSocket error forwarded to the raw socket must not throw');

    test.doesNotThrow(() => {
        client._deflate.emit('error', new Error('deflate failure'));
    }, 'deflate error forwarded to the raw socket must not throw');

    // close() must remain safe to call after unbind().
    test.doesNotThrow(() => {
        client.close();
    }, 'close() after unbind must not throw');

    test.done();
};

module.exports['Connection Edge: resolveRange with number input'] = async test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    client.mailbox = { exists: 10 };
    let options = {};

    let result = await client.resolveRange(123, options);
    test.equal(result, '123');
    test.done();
};

module.exports['Connection Edge: resolveRange with bigint input'] = async test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    client.mailbox = { exists: 10 };
    let options = {};

    let result = await client.resolveRange(BigInt(456), options);
    test.equal(result, '456');
    test.done();
};

module.exports['Connection Edge: resolveRange with star and existing messages'] = async test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    client.mailbox = { exists: 50 };
    let options = { uid: true };

    let result = await client.resolveRange('*', options);
    test.equal(result, '50');
    test.equal(options.uid, false); // should be changed to sequence query
    test.done();
};

module.exports['Connection Edge: resolveRange with star and no messages'] = async test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    client.mailbox = { exists: 0 };
    let options = {};

    let result = await client.resolveRange('*', options);
    test.equal(result, false);
    test.done();
};

module.exports['Connection Edge: resolveRange with {all: true}'] = async test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    client.mailbox = { exists: 10 };
    let options = {};

    let result = await client.resolveRange({ all: true }, options);
    test.equal(result, '1:*');
    test.done();
};

module.exports['Connection Edge: resolveRange with {uid: value}'] = async test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    client.mailbox = { exists: 10 };
    let options = {};

    let result = await client.resolveRange({ uid: '1:100' }, options);
    test.equal(result, '1:100');
    test.equal(options.uid, true);
    test.done();
};

module.exports['Connection Edge: resolveRange with array input'] = async test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    client.mailbox = { exists: 10 };
    let options = {};

    let result = await client.resolveRange(['1', '2', '3'], options);
    test.equal(result, '1,2,3');
    test.done();
};

module.exports['Connection Edge: resolveRange with empty/falsy input'] = async test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    client.mailbox = { exists: 10 };
    let options = {};

    let result = await client.resolveRange('', options);
    test.equal(result, false);

    result = await client.resolveRange(null, options);
    test.equal(result, false);

    test.done();
};

module.exports['Connection Edge: ensureSelectedMailbox with no path'] = async test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    let result = await client.ensureSelectedMailbox('');
    test.equal(result, false);

    result = await client.ensureSelectedMailbox(null);
    test.equal(result, false);

    test.done();
};

module.exports['Connection Edge: ensureSelectedMailbox when already selected'] = async test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    client.mailbox = { path: 'INBOX' };

    let result = await client.ensureSelectedMailbox('INBOX');
    test.equal(result, true);
    test.done();
};

module.exports['Connection Edge: autoidle clears existing timer'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Set a fake timer
    client.idleStartTimer = setTimeout(() => {}, 10000);
    client.state = client.states.NOT_AUTHENTICATED;

    // Call autoidle - should clear the timer but not set new one (wrong state)
    client.autoidle();

    // Should have been cleared
    test.ok(true);
    test.done();
};

module.exports['Connection Edge: autoidle disabled by option'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' },
        disableAutoIdle: true
    });

    client.state = client.states.SELECTED;

    // Call autoidle - should return early due to disableAutoIdle
    client.autoidle();

    test.equal(client.idleStartTimer, undefined);
    test.done();
};

module.exports['Connection Edge: getUntaggedHandler with numeric command'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Set up a current request with untagged handler
    client.currentRequest = {
        options: {
            untagged: {
                FETCH: () => 'fetch handler'
            }
        }
    };

    // Test with numeric command and FETCH attribute
    let handler = client.getUntaggedHandler('123', [{ value: 'FETCH' }]);
    test.equal(handler(), 'fetch handler');

    test.done();
};

module.exports['Connection Edge: getUntaggedHandler from untaggedHandlers'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Set up global untagged handler
    client.untaggedHandlers = {
        CAPABILITY: () => 'capability handler'
    };

    let handler = client.getUntaggedHandler('CAPABILITY', []);
    test.equal(handler(), 'capability handler');

    test.done();
};

module.exports['Connection Edge: getSectionHandler returns handler'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Set up section handler
    client.sectionHandlers = {
        CAPABILITY: () => 'section handler'
    };

    let handler = client.getSectionHandler('CAPABILITY');
    test.equal(handler(), 'section handler');

    // Test non-existent handler
    handler = client.getSectionHandler('NONEXISTENT');
    test.equal(handler, undefined);

    test.done();
};

module.exports['Connection Edge: constructor with custom clientInfo'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' },
        clientInfo: {
            name: 'CustomClient',
            version: '1.0.0'
        }
    });

    test.equal(client.clientInfo.name, 'CustomClient');
    test.equal(client.clientInfo.version, '1.0.0');
    test.ok(client.clientInfo.vendor); // Should still have default vendor
    test.done();
};

module.exports['Connection Edge: constructor with secure port 993 detection'] = test => {
    // When port is 993 and secure is undefined, should default to secure
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    test.equal(client.secureConnection, true);
    test.done();
};

module.exports['Connection Edge: constructor normalizes clientInfo diacritics'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' },
        clientInfo: {
            name: 'Café Clïent',
            version: '1.0.0'
        }
    });

    // Diacritics should be removed (NFD normalization splits é into e + accent, then accent is removed)
    test.equal(client.clientInfo.name, 'Cafe Client');
    test.done();
};

module.exports['Connection Edge: constructor with servername option'] = test => {
    let client = new ImapFlow({
        host: '192.168.1.1',
        port: 993,
        auth: { user: 'test', pass: 'test' },
        servername: 'mail.example.com'
    });

    test.equal(client.servername, 'mail.example.com');
    test.done();
};

module.exports['Connection Edge: constructor servername from host when not IP'] = test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    test.equal(client.servername, 'imap.example.com');
    test.done();
};

module.exports['Connection Edge: constructor servername false for IP host'] = test => {
    let client = new ImapFlow({
        host: '192.168.1.1',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    test.equal(client.servername, false);
    test.done();
};

module.exports['Connection Edge: connect throws if called twice'] = async test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Mark as already called
    client._connectCalled = true;

    try {
        await client.connect();
        test.ok(false, 'Should have thrown');
    } catch (err) {
        test.ok(err.message.includes('re-use'));
    }

    test.done();
};

// Helper to create a mock client with compression enabled
async function setupCompressedClient() {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    let mockSocket = new EventEmitter();
    mockSocket.pipe = dest => dest;
    mockSocket.unpipe = () => {};
    mockSocket.destroy = () => {};
    mockSocket.destroyed = false;
    client.socket = mockSocket;
    // Mock streamer that tracks destruction like a real stream, so close() drives it into
    // the destroyed state the post-close guards actually check.
    client.streamer = Object.assign(new EventEmitter(), {
        destroyed: false,
        destroy() {
            this.destroyed = true;
        }
    });

    client.run = async command => {
        if (command === 'COMPRESS') {
            return true;
        }
    };

    await client.compress();
    return { client, mockSocket };
}

module.exports['Connection Edge: compress writeSocket error forwarded to socket when live'] = async test => {
    let { client, mockSocket } = await setupCompressedClient();

    let errorReceived = false;
    mockSocket.on('error', err => {
        errorReceived = true;
        test.equal(err.message, 'writeSocket error');
    });

    client.writeSocket.emit('error', new Error('writeSocket error'));

    test.ok(errorReceived, 'Error should be forwarded to socket');
    test.done();
};

module.exports['Connection Edge: compress _deflate error forwarded to socket when live'] = async test => {
    let { client, mockSocket } = await setupCompressedClient();

    let errorReceived = false;
    mockSocket.on('error', err => {
        errorReceived = true;
        test.equal(err.message, 'deflate error');
    });

    client._deflate.emit('error', new Error('deflate error'));

    test.ok(errorReceived, 'Error should be forwarded to socket');
    test.done();
};

module.exports['Connection Edge: compress readable after close does not crash'] = async test => {
    let { client } = await setupCompressedClient();

    // Save reference before close() nulls it
    let writeSocket = client.writeSocket;

    client.close();

    // Emit readable on the saved reference after close has nulled this.writeSocket
    // This should not throw (guards at lines 1048 and 1021)
    test.doesNotThrow(() => {
        writeSocket.emit('readable');
    });

    test.done();
};

module.exports['Connection Edge: compress writeSocket error after close does not crash'] = async test => {
    let { client } = await setupCompressedClient();

    let writeSocket = client.writeSocket;

    client.close();

    // Emit error on saved writeSocket after close has nulled this.socket
    // This should not throw (guard at lines 1053-1055)
    test.doesNotThrow(() => {
        writeSocket.emit('error', new Error('late writeSocket error'));
    });

    test.done();
};

module.exports['Connection Edge: compress _deflate error after close does not crash'] = async test => {
    let { client } = await setupCompressedClient();

    let deflate = client._deflate;

    client.close();

    // Emit error on saved _deflate after close has nulled this.socket
    // This should not throw (guard at lines 1060-1062)
    test.doesNotThrow(() => {
        deflate.emit('error', new Error('late deflate error'));
    });

    test.done();
};

module.exports['Connection Edge: compress _inflate error after close does not crash'] = async test => {
    let { client } = await setupCompressedClient();

    let inflate = client._inflate;

    // close() removes the streamer 'error' listener and destroys the streamer.
    // A late inflate error must not be forwarded into the destroyed streamer
    // (which would throw an unhandled 'error' and crash the process).
    client.close();

    // Confirm we actually reached the post-close state the guard targets.
    test.ok(client.streamer.destroyed, 'streamer is destroyed after close()');

    // Re-attach an 'error' listener so listenerCount('error') > 0: this isolates the
    // `!streamer.destroyed` term of the guard. If forwarding still happened, this listener
    // would throw and escape doesNotThrow — proving the destroyed-check is what suppresses it.
    client.streamer.on('error', () => {
        throw new Error('inflate error must not be forwarded into a destroyed streamer');
    });

    test.doesNotThrow(() => {
        inflate.emit('error', new Error('late inflate error'));
    });

    test.done();
};

// ============================================
// Mailbox Lock Tests
// ============================================

module.exports['Connection Edge: getMailboxLock queues lock in locks array'] = async test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // getMailboxLock should push to locks array then reject (no connection)
    try {
        await client.getMailboxLock('INBOX');
        test.ok(false, 'Should have thrown');
    } catch (err) {
        test.ok(err);
        test.equal(err.code, 'NoConnection');
    }

    test.done();
};

module.exports['Connection Edge: lockCounter increments with each getMailboxLock call'] = async test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    let initial = client.lockCounter;
    let p1 = client.getMailboxLock('INBOX').catch(() => {});
    let p2 = client.getMailboxLock('Sent').catch(() => {});

    test.equal(client.lockCounter, initial + 2);

    await Promise.all([p1, p2]);
    test.done();
};

module.exports['Connection Edge: lock rejected when connection not usable'] = async test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // usable is false by default, no socket
    try {
        await client.getMailboxLock('INBOX');
        test.ok(false, 'Should have thrown');
    } catch (err) {
        test.ok(err);
        test.equal(err.code, 'NoConnection');
        test.ok(err.message.includes('not available'));
    }
    test.done();
};

module.exports['Connection Edge: processLocks with empty locks array completes'] = async test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // processLocks with no pending locks should complete without error
    await client.processLocks();
    test.equal(client.processingLock, false);
    test.done();
};

module.exports['Connection Edge: multiple locks rejected when no connection'] = async test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' }
    });

    // Queue multiple locks, all should reject
    let errors = [];
    let p1 = client.getMailboxLock('INBOX').catch(err => errors.push(err));
    let p2 = client.getMailboxLock('Sent').catch(err => errors.push(err));
    let p3 = client.getMailboxLock('Drafts').catch(err => errors.push(err));

    await Promise.all([p1, p2, p3]);

    test.equal(errors.length, 3);
    for (let err of errors) {
        test.equal(err.code, 'NoConnection');
    }
    test.done();
};

// ============================================
// reader() survives a write() throw without stalling the stream
// ============================================

module.exports['Connection Edge: reader survives write throw on + continuation'] = async test => {
    let client = new ImapFlow({
        host: 'imap.example.com',
        port: 993,
        auth: { user: 'test', pass: 'test' },
        logger: false
    });

    client.currentRequest = false;
    client.commandParts = [Buffer.from('literal-bytes')];

    let writeAttempted = false;
    client.write = () => {
        writeAttempted = true;
        throw new Error('write boom');
    };

    let nextCalled = false;
    let done = false;
    client.streamer.read = () => {
        if (done) {
            return null;
        }
        done = true;
        return {
            payload: Buffer.from('+ Ready'),
            literals: [],
            next: () => {
                nextCalled = true;
            }
        };
    };

    let rejected = false;
    await client.reader().catch(() => {
        rejected = true;
    });

    test.ok(writeAttempted, 'the literal-continuation write path was exercised');
    test.equal(rejected, false, 'reader did not reject on write throw');
    test.ok(nextCalled, 'data.next() was still called so the stream is not stalled');
    test.done();
};

// ============================================
// BYE greeting surfaces its reason on the connect rejection
// ============================================

module.exports['Connection Edge: BYE greeting surfaces reason on connect rejection'] = async test => {
    // Deterministic ordering: the server ends the socket only AFTER the client has parsed the
    // BYE and set byeReason, instead of racing a fixed 50ms timer that could flake on slow CI.
    let signalByeParsed;
    let byeParsed = new Promise(resolve => {
        signalByeParsed = resolve;
    });

    let server = net.createServer(socket => {
        socket.on('error', () => {});
        socket.write('* BYE Server too busy\r\n');
        byeParsed.then(() => socket.end());
    });

    await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
    let port = server.address().port;

    let client = new ImapFlow({
        host: '127.0.0.1',
        port,
        secure: false,
        disableAutoIdle: true,
        logger: false,
        auth: { user: 'test', pass: 'test' }
    });
    client.on('error', () => {});

    // Signal once the client's BYE handler has run, so byeReason is guaranteed set before close().
    let origServerBye = client.serverBye.bind(client);
    client.serverBye = async parsed => {
        await origServerBye(parsed);
        signalByeParsed();
    };

    let connectErr = null;
    try {
        await client.connect();
        test.ok(false, 'connect() should reject after a BYE greeting');
    } catch (err) {
        connectErr = err;
    }

    test.ok(connectErr, 'connect() rejected');
    test.equal(connectErr.reason, 'Server too busy', 'BYE reason surfaced on the rejection');

    client.close();
    server.close();
    test.done();
};
