import { MessageInterface, MessageListener } from '../communication-interface/MessageInterface';

import { SignalProtocolAddress } from '@privacyresearch/libsignal-protocol-typescript';


import { SignalUtil } from './SignalUtil';
import { TransportInterface } from '../communication-interface/TransportInterface';
import { DBFactory } from '../db/DBFactory';

export class LibsignalMessage implements MessageInterface {
    private transport: TransportInterface;
    private _signalUtil: SignalUtil;
    private _messageCallbacks: MessageListener[] = [];

    constructor(transport: TransportInterface, dbFactory?: DBFactory) {
        this.transport = transport;
        this._signalUtil = new SignalUtil(dbFactory);
        void this.transport.addMessageListener(async message => {
            try {
                if (typeof message === 'string') {
                    message = JSON.parse(message);
                }
                const { content, from } = message;
                const { payload, transportAddress } = JSON.parse(await this._signalUtil.decryptFrom(this._userIdToAddress(from), content));
                //transport implementations, that do not rely on bootstrapping using a different transport can use transportAddress to establish a return channel
                if (transportAddress) {
                    await this.transport.addContact(from, transportAddress);
                }
                if (this._messageCallbacks.length > 0) {
                    this._messageCallbacks.forEach(cb => cb(from, payload));
                }
            } catch (err) {
                console.error(err);
            }
        });
    }

    async getKeyInformation(): Promise<any> {
        await this._signalUtil.getOwnUserId();
        return await this._signalUtil.getPreKeyBundle();
    }

    async getOwnUserId(): Promise<string> {
        return await this._signalUtil.getOwnUserId();
    }

    private _userIdToAddress(userId: string) {
        return SignalProtocolAddress.fromString(userId);
    }

    async addContact(userId: string, keyInformation: any): Promise<void> {
        await this._signalUtil.addContact(this._userIdToAddress(userId), keyInformation);
    }

    async send(userId: string, payload: string): Promise<void> {
        const address = this._userIdToAddress(userId);
        const transportAddress = this.transport.requiresBootstrap() ? undefined : await this.transport.getOwnAddress();
        const encrypted = await this._signalUtil.encryptTo(address, JSON.stringify({ payload, transportAddress }));
        const messageToOtherUser = JSON.stringify({ content: encrypted, from: await this.getOwnUserId() });
        return await this.transport.send(userId, messageToOtherUser);
    }

    async sendToGroup(groupId: string, payload: string): Promise<void> {
        throw new Error('Method not implemented.');
    }

    addMessageListener(listener: MessageListener): void | Promise<void> {
        this._messageCallbacks.push(listener);
    }

}