import WebSocket from 'ws';

/**
 * Uses the webhook proxy service to proxy events to the testing clients.
 */
export default class WebhookProxy {
    private url;
    private secret;
    private ws: WebSocket | undefined;
    private cache = new Map();
    private listeners = new Map();
    private consumers = new Map();

    /**
     * Initializes the webhook proxy.
     * @param url
     * @param secret
     */
    constructor(url: string, secret: string) {
        this.url = url;
        this.secret = secret;
    }

    /**
     * Connects.
     */
    connect() {
        this.ws = new WebSocket(this.url, {
            headers: {
                Authorization: this.secret
            }
        });

        this.ws.on('error', console.error);

        this.ws.on('open', function open() {
            console.log('WebhookProxy connected');
        });

        this.ws.on('message', (data: any) => {
            const msg = JSON.parse(data.toString());

            if (msg.eventType) {
                if (this.consumers.has(msg.eventType)) {
                    this.consumers.get(msg.eventType)(msg);
                    this.consumers.delete(msg.eventType);
                } else {
                    this.cache.set(msg.eventType, msg);
                }

                if (this.listeners.has(msg.eventType)) {
                    this.listeners.get(msg.eventType)(msg);
                }
            }
        });
    }

    /**
     * Adds event consumer. Consumers receive the event single time and we remove them from the list of consumers.
     * @param eventType
     * @param callback
     */
    addConsumer(eventType: string, callback: (deventata: any) => void) {
        if (this.cache.has(eventType)) {
            callback(this.cache.get(eventType));
            this.cache.delete(eventType);

            return;
        }

        this.consumers.set(eventType, callback);
    }

    /**
     * Clear any stored event.
     */
    clearCache() {
        this.cache.clear();
    }

    /**
     * Waits for the event to be received.
     * @param eventType
     * @param timeout
     */
    async waitForEvent(eventType: string, timeout = 4000): Promise<any> {
        // we create the error here so we have a meaningful stack trace
        const error = new Error(`Timeout waiting for event:${eventType}`);

        return new Promise((resolve, reject) => {
            const waiter = setTimeout(() => reject(error), timeout);

            this.addConsumer(eventType, event => {
                clearTimeout(waiter);

                resolve(event);
            });

        });
    }

    /**
     * Adds a listener for the event type.
     * @param eventType
     * @param callback
     */
    addListener(eventType: string, callback: (data: any) => void) {
        this.listeners.set(eventType, callback);
    }

    /**
     * Adds a listener for the event type.
     * @param eventType
     */
    removeListener(eventType: string) {
        this.listeners.delete(eventType);
    }

    /**
     * Disconnects the webhook proxy.
     */
    disconnect() {
        if (this.ws) {
            this.ws.close();
            console.log('WebhookProxy disconnected');
            this.ws = undefined;
        }
    }
}
