/*
 * Copyright (c) 2022.
 * Author Peter Placzek (tada5hi)
 * For the full copyright and license information,
 * view the LICENSE file that was distributed with this source code.
 */

import {Context} from "@nuxt/types";
import {Socket} from "socket.io-client";
import {SecurityKeyPair} from "~/modules/security";
import {decodeContactMessage, encodeContactMessage} from "~/domains/contact/encryption";
import {decodeChatRoomMessage, encodeChatRoomMessage} from "~/domains/chat-room/encryption";

export interface ChatRoom extends Record<string, any> {
    id: string,
    contactId: string | null,
    [key: string]: any
}

export interface ChatRoomRegistration extends Record<string, any> {
    subscriptions: number,
    room: {
        id: string,
        contactId: string | null,
        [key: string]: any
    }
}

export class ChatModule {
    protected ctx: Context;

    private roomRegistrations: Map<string,ChatRoomRegistration>;

    public socket: Socket;

    constructor(ctx: Context) {
        this.ctx = ctx;

        this.roomRegistrations = new Map<string, ChatRoomRegistration>();

        this.socket = this.ctx.$socket.use('/chat');
        this.initSocketHandler();

        this.subscribeStore();
    }

    //-------------------------------------------------------

    async joinRoom(room: ChatRoom) {
        return new Promise(((resolve, reject) => {
            const connectionTimeout = setTimeout(() => {
                return reject(new Error('Die Verbindung konnte nicht aufgebaut werden...'));
            },10000);

            return this.socket.emit('join', {
                id: room.id,
                type: 'chat-room'
            }, (data) => {
                clearTimeout(connectionTimeout);

                if(data.success) {
                    return this.registerRoom(room).then(() => {
                        return resolve(data.data);
                    }).catch(reject);
                } else {
                    return reject(new Error(data.message));
                }
            });
        }));
    }

    async leaveRoom(roomId: string) {
        return new Promise(((resolve, reject) => {
            const connectionTimeout = setTimeout(() => {
                return reject(new Error('Die Verbindung konnte nicht getrennt werden...'));
            },10000);

            return this.socket.emit('leave', {
                id: roomId,
                type: 'chat-room',
            }, (data) => {
                clearTimeout(connectionTimeout);

                this.unregisterRoom(roomId);

                this.ctx.store.dispatch('chat/dropRoomMemberOnline', roomId).then(r => r);

                if(data.success) {
                    return resolve(data.success);
                } else {
                    return reject(new Error(data.message));
                }
            });
        }));
    }

    public getRegistration(roomId: string) : ChatRoomRegistration {
        const room : undefined | ChatRoomRegistration = this.roomRegistrations.get(roomId);
        if(typeof room === 'undefined') {
            throw new Error('Der Raum #'+roomId+' wurde noch nicht registriert.');
        }

        return room;
    }

    private setRegistration(roomId: string, registration: ChatRoomRegistration) {
        this.roomRegistrations.set(roomId, registration);
    }

    public async registerRoom(room: ChatRoom) {
        if(this.roomRegistrations.has(room.id)) {
            const registration = <ChatRoomRegistration> this.roomRegistrations.get(room.id);
            registration.subscriptions++;

            this.setRegistration(room.id, registration);
            return;
        }


        const registration : ChatRoomRegistration = {
            subscriptions: 1,
            room: room
        };

        this.setRegistration(room.id, registration);
        this.loadSockets(room.id).then(r => r);
        //await this.initEncryption(room.id);
    }

    public unregisterRoom(roomId: string) {
        if(this.roomRegistrations.has(roomId)) {
            const room = <ChatRoomRegistration> this.roomRegistrations.get(roomId);
            room.subscriptions--;

            if(room.subscriptions === 0) {
                this.roomRegistrations.delete(roomId);
            } else {
                this.setRegistration(roomId, room);
            }
        }
    }

    //-------------------------------------------------------
    // Socket
    //-------------------------------------------------------

    private initSocketHandler() {
        const handler = (event, data) => {
            switch (event) {
                case 'userOnline':
                    this.handleSocketUserOnlineEvent(data);
                    break;
                case 'userOffline':
                    this.handleSocketUserOfflineEvent(data);
                    break;
                case 'reconnect':
                    this.handleSocketReconnect();
                    break;
                case 'connectionClose':
                    this.handleSocketConnectionCloseEvent(data);
                    break;
            }
        };

        handler.bind(this);
        this.socket.onAny(handler);
    }

    private async loadAllSockets() {
        await this.roomRegistrations.forEach((async (value, key) => {
            await this.loadSockets(key);
        }))
    }

    private async loadSockets(roomId: string) {
        return new Promise(((resolve, reject) => {
            this.socket.emit('onlineUsers', {
                id: roomId,
                type: 'chat-room',
            }, (response) => {
                if(typeof response === 'object') {
                    if (response.success) {
                        const {data} = response.data;
                        const items : Record<string, any>[] = [];

                        for (let i = 0; i < data.length; i++) {
                            items.push({id: data[i].id, roomId: roomId});
                        }

                        this.ctx.store.dispatch('chat/setMembersOnline', items).then(r => r);

                        resolve(data);
                    } else {
                        reject(new Error());
                    }
                }
            });
        }));
    }

    private handleSocketReconnect() {
        this.loadAllSockets().then(r => r);
    }

    private handleSocketConnectionCloseEvent(data) {
        this.ctx.store.dispatch('chat/dropRoomMemberOnline', data.id).then(r => r);
    }

    private handleSocketUserOnlineEvent(data) {
        this.ctx.store.dispatch('chat/addMemberOnline', {roomId: data.meta.roomId, id: data.id}).then(r => r);
    }

    private handleSocketUserOfflineEvent(data) {
        this.ctx.store.dispatch('chat/dropMemberOnline', {roomId: data.meta.roomId, id: data.id}).then(r => r);
    }

    //-------------------------------------------------------
    // Security
    //-------------------------------------------------------

    encodeMessage(roomId: string, message: string) {
        const registration = this.getRegistration(roomId);

        if(registration.room.contactId) {
            return encodeContactMessage(message, registration.room.contactId);
        } else {
            return encodeChatRoomMessage(message, registration.room.id);
        }
    }

    decodeMessage(roomId: string, message: string) {
        const registration = this.getRegistration(roomId);

        if(registration.room.contactId) {
            return decodeContactMessage(message, registration.room.contactId);
        } else {
            return decodeChatRoomMessage(message, registration.room.id);
        }
    }

    //-------------------------------------------------------

    private subscribeStore() {
        this.ctx.store.subscribe((mutation: any) => {
            switch (mutation.type) {
                case 'auth/unsetToken':
                    this.ctx.$socket.reconnect('/chat');
                    break;
                case 'auth/setToken':
                    this.ctx.$socket.reconnect('/chat');
                    break;
            }
        });
    }
}
