import { TransportInterface } from '../communication-interface/TransportInterface';
import { getPushRegistrationJSON, sendPushNotification, registerCallback } from './PushRegistration';

import { PushTransportDto } from '../libDto/PushTransportDto';
import { ObjectStore } from '../db/DBAccess';
import { DBFactory } from '../db/DBFactory';
import { PersistentTransport } from '../communication-interface/PersistentTransport';


type MessageCallback = (payload: string) => void;

export class PushTransport implements TransportInterface, PersistentTransport {
    private _pushTransportDto?: ObjectStore<PushTransportDto>;
    private _initPromise: Promise<void>;
    private _messageQueue: any[] = [];
    private _messageCallbacks: MessageCallback[] = [];

    private constructor(private dbFactory: DBFactory) {
        this.dbFactory = dbFactory;
        this._initPromise = this.init();
    }
    requiresBootstrap(): boolean {
        return false;
    }

    private static instance: PushTransport;
    public static getInstance(dbFactory: DBFactory) {
        if (!PushTransport.instance) {
            PushTransport.instance = new PushTransport(dbFactory);
        }
        return PushTransport.instance;
    }

    async init() {
        await registerCallback(evt => {
            this._messageQueue.push(evt.data);
            void this._flushMessages();
        });
        this._pushTransportDto = await this.dbFactory.getDao<ObjectStore<PushTransportDto>>(PushTransportDto);
    }

    async getOwnAddress(): Promise<string> {
        return await getPushRegistrationJSON();
    }

    async addContact(userId: string, address: any): Promise<void> {
        await this._initPromise;
        const existingEntry = await this._pushTransportDto!.get(userId);
        if (existingEntry) {
            if (existingEntry.pushRegistration !== address) {
                console.info(`updating push registration for user ${userId}`);
                await this._pushTransportDto!.update(new PushTransportDto(userId, address));
            }
        } else {
            await this._pushTransportDto!.add(new PushTransportDto(userId, address));
        }
    }

    async send(userIdTo: string, message: string): Promise<void> {
        const pushTransport = await this._pushTransportDto!.get(userIdTo);
        if (!pushTransport) {
            throw new Error(`Unknown user id ${userIdTo}. Please add the user using the addContact method first.`);
        }
        try {
            await sendPushNotification(pushTransport.pushRegistration, message);
        } catch (e: any) {
            if (e.message == 'Invalid Registration') {
                await this._pushTransportDto!.delete(pushTransport);
                throw new Error(`Invalid registration for user ${userIdTo}. Please add the user using the addContact method first.`);
            }
            else {
                throw e;
            }
        }
    }

    addMessageListener(listener: (payload: string) => void): void | Promise<void> {
        this._messageCallbacks.push(listener);
        void this._flushMessages();
    }

    private async _flushMessages() {
        if (this._messageCallbacks.length > 0) {
            while (this._messageQueue.length > 0) {
                let message = this._messageQueue.shift();
                if (message.url) {
                    const content = await fetch(message.url).then(r => r.text());
                    message = content;
                }

                if (message) {
                    this._messageCallbacks.forEach(cb => cb(message!));
                } else {
                    throw new Error('Message is null');
                }
            }
        }
    }

    async getAllContacts() {
        await this._initPromise;
        return (await this._pushTransportDto!.getAll()).map(c => c.id);
    }

}