import { MessageInterface, MessageListener } from '../src';
import { LongAsyncRemoteFunction } from '../src/lib/objectTransport/LongAsyncRemoteFunction';

class DummyMessage implements MessageInterface {
    private mls: MessageListener[] = [];

    async send(userId: string, payload: string): Promise<void> {
        this.mls.forEach((li: MessageListener) => li(userId, payload));
    }


    addMessageListener(listener: MessageListener): void {
        this.mls.push(listener);
    }
    async addContact(userId: string, keyInformation: any): Promise<string | void> {
        //lol
    }
    async getKeyInformation(): Promise<any> {
        return '';
    }
    async getOwnUserId(): Promise<string> {
        return '';
    }

}

type TestParameterMap = {
    'add': { a: number, b: number },
    'addResult': { result: number }
}

/**
 * This test suite implicitly tests that communication works cross origin as localhost:3000 is a different origin to 127.0.0.1:3000.
 */
describe('RTCPushMessage', () => {

    it('should call the function on the other side', async () => {
        expect.assertions(1);
        const larf = new LongAsyncRemoteFunction<TestParameterMap>(new DummyMessage());
        larf.register('add', async (from, { a, b }) => {
            await larf.call('addResult', from, { result: a + b });
        });
        larf.register('addResult', async (from, { result }) => {
            expect(result).toBe(3);
        });
        await larf.call('add', 'other', { a: 1, b: 2 }, { functionName: 'addResult', timeout: 1000 });
    });

    it('should throw an error if the function is not registered', async () => {
        expect.assertions(1);
        const larf = new LongAsyncRemoteFunction<TestParameterMap>(new DummyMessage());
        try {
            await larf.call('add', 'other', { a: 1, b: 2 }, { functionName: 'addResult', timeout: 1000 });
        } catch (err: any) {
            expect(err.message).toBe('function add not registered');
        }
    });

    it('should throw an error if another function is not invoked', async () => {
        expect.assertions(1);
        const larf = new LongAsyncRemoteFunction<TestParameterMap>(new DummyMessage());
        larf.register('add', async (from, { a, b }) => {
            console.log(a + b);//not calling addResult
        });
        larf.register('addResult', async (from, { result }) => {
            console.log(result);//won't be called
        });
        try {
            await larf.call('add', 'other', { a: 1, b: 2 }, { functionName: 'addResult', timeout: 1000 });
        } catch (err: any) {
            expect(err.message).toBe('Expected call to addResult from other timed out');
        }
    });

    it('should propagate errors back to caller in callback chain', async () => {
        expect.assertions(1);
        const larf = new LongAsyncRemoteFunction<TestParameterMap>(new DummyMessage());
        larf.register('add', async (from, { a, b }) => {
            await larf.call('addResult', from, { result: a + b });
        });
        larf.register('addResult', async (from, { result }) => {
            throw new Error('Test');
        });
        try {
            await larf.call('add', 'other', { a: 1, b: 2 }, { functionName: 'addResult', timeout: 1000 });
        } catch (err: any) {
            expect(err.message).toBe('Test');
        }
    });

    it('should respect namespaces', async () => {
        expect.assertions(1);
        const mi = new DummyMessage()
        const larf1 = new LongAsyncRemoteFunction<TestParameterMap>(mi, 'test');
        const larf2 = new LongAsyncRemoteFunction<TestParameterMap>(mi, 'asdf');

        larf1.register('add', async (from, { a, b }) => {
            await larf1.call('addResult', from, { result: a + b });
        });
        larf1.register('addResult', async (from, { result }) => {
            expect(result).toBe(3);
        });


        larf2.register('add', async (from, { a, b }) => {
            throw new Error('Should not be called');
        });
        larf2.register('addResult', async (from, { result }) => {
            throw new Error('Should not be called');
        });

        await larf1.call('add', 'other', { a: 1, b: 2 }, { functionName: 'addResult', timeout: 1000 });
    });
});
