import type { UniplatSdk } from "uniplat-sdk";

import { EmojiService } from "../service/emoji";

import {
    ChatOption,
    TokenStringGetter,
    ServiceType,
    CustomerServiceProduct,
} from "./../model";
import { ChatLoggerService } from "./logger";
import tokenManager from "./token";
import xim from "./xim";
import { dbController } from "../database";

class Chat {
    private _sdk?: () => UniplatSdk;
    private _orgId: () => string | number = () => "0";
    private token!: TokenStringGetter;
    private serviceType = ServiceType.Backend;
    private product = CustomerServiceProduct.Default;
    private eventHub: Vue | null = null;
    private keywords: string[] = [];
    private ws = "";
    private connectedActions: (() => void)[] = [];
    private connected = false;

    private userMapping: { [key: string]: { name: string; avatar: string } } =
        {};

    public onReady(action: () => void) {
        if (this.connected) {
            action();
        } else {
            this.connectedActions.push(action);
        }
    }

    public async setup(option: ChatOption) {
        if (!option) {
            throw new Error(`You must specify a chat option for chat service`);
        }

        if (!option.webSocketUri) {
            throw new Error(
                `You must specify a web socket address for chat service`
            );
        }
        this._sdk = option.sdk;
        this._orgId = option.orgId;
        option.serviceType !== undefined &&
            (this.serviceType = option.serviceType);
        option.product && (this.product = option.product);
        this.eventHub = option.eventHub || null;
        if (option.user) {
            this.userMapping[this._sdk().global.uid] = {
                name: option.user.username || "",
                avatar: option.user.icon || "",
            };
        }

        this.setupIndexDb();
        this.token = async () => option.sdk().global.jwtToken;
        tokenManager.save(this.token);

        EmojiService.raiseOnReady(this.token);

        option
            .sdk()
            .events.addTokenChanged((token) =>
                this.setToken(() => new Promise((resolve) => resolve(token)))
            );

        // this.keywords = ["社保"];

        return this.initChatSdk((this.ws = option.webSocketUri)).finally(() => {
            this.connected = true;
            for (const item of this.connectedActions) {
                item();
            }
        });
    }

    private setupIndexDb() {
        if (this._sdk) {
            const s = this._sdk();
            return dbController.setup(
                s.global.uid + "-" + (s.global.initData.orgId || 0)
            );
        }
        return Promise.reject();
    }

    public resetup(org: () => string | number) {
        this._orgId = org;
        xim.onConnected();
        return this.setupIndexDb();
    }

    public unSetup() {
        xim.close();
    }

    public getSdk = () => {
        if (!this._sdk) {
            throw new Error("sdk shouldn't undefined");
        }
        return this._sdk();
    };

    public getServiceType() {
        return this.serviceType;
    }

    public isBackend() {
        return this.serviceType === ServiceType.Backend;
    }

    public getProduct() {
        return this.product;
    }

    public getOrgId = () => {
        return this._orgId();
    };

    public setToken(token: TokenStringGetter) {
        return xim.setToken(token);
    }

    private trimToken(token: string) {
        return token.replace(/^Bearer\s/, "");
    }

    public async getToken() {
        return this.trimToken(await this.token());
    }

    private async initChatSdk(uri: string) {
        if (xim.isConnected()) {
            return Promise.resolve(uri);
        }
        return new Promise((resolve) => {
            xim.open(uri, this.token).finally(() => {
                this.registerXimEvent();
                if (xim.isConnected()) {
                    resolve();
                } else {
                    setTimeout(() => resolve(), 2000);
                }
            });
        });
    }

    public registerXimEvent(onConnected?: () => void) {
        xim.off("status", (e) => this.raiseOnStatusChanged(e, onConnected));
        xim.on("status", (e) => this.raiseOnStatusChanged(e, onConnected));
    }

    private raiseOnStatusChanged(e: any, onConnected?: () => void) {
        if (e === "CONNECTED") {
            if (onConnected) {
                onConnected();
            }
        }

        this.debug(`client status ${e}`);
    }

    public getUserMapping() {
        return this.userMapping;
    }

    private debug(message: string) {
        ChatLoggerService.logger?.debug(message);
    }

    public $emit(event: string, ...args: any[]) {
        if (this.eventHub) {
            this.eventHub.$emit(event, ...args);
        }
    }

    public $on(event: string, callback: Function) {
        if (this.eventHub) {
            this.eventHub.$on(event, callback);
        }
    }

    public getMatchedTextKeywords() {
        return this.keywords;
    }
}

export default new Chat();