import Vue from "vue"
import { XChatClient, wampDebug } from "xchat-client"
import { NotifyMessage, Message } from "./models/chat"
import { ChatLoggerService } from "./logger"
import { TokenStringGetter } from "./../model"
import chatType from "../xim/chat-type"

wampDebug(true)

const DefaultMsgPageSize = 20

function emptyFunc() {
    return
}

export type MsgListener = (msg: Message) => void

export type ChatNotifyListener = (msg: NotifyMessage) => void

export type StatusChangeListener = (status: any, details: any) => void

export enum Events {
    Msg = "msg",
    Status = "status",
}

export enum Kind {
    Chat = "chat",
    ChatNotify = "chat_notify",
    UserNotify = "user_notify",
}

export class Xim {
    private eventBus = new Vue()

    private client?: XChatClient

    private paramsForReconnection?: {
        url: string
        token: TokenStringGetter
    }

    public close() {
        if (this.client) {
            if (this.client.connected) {
                this.client.close()
            }

            this.client.onconnected = emptyFunc
            this.client.onmsg = emptyFunc
            this.client = undefined
        }
    }

    private connectionPending = false

    public async open(url: string, token: TokenStringGetter) {
        this.connectionPending = true
        await new Promise((success: (p?: unknown) => void, failed) => {
            this.paramsForReconnection = { url, token }
            this.close()

            token().then((t) => {
                const client = new XChatClient(url, this.trimToken(t))
                this.client = client

                client.onstatuschange = (status: any, details: any) => {
                    this.onStatusChange.call(this, status, details)
                    if (status === "DISCONNECTED" || status === "CLOSED") {
                        failed()
                    }
                }

                client.onconnected = () => {
                    this.onConnected.apply(this)
                    success()
                }

                client.onmsg = this.handleMsg.bind(this)
                client.open()
            })
        }).finally(() => (this.connectionPending = false))
    }

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

    /**
     * token过期或者切换用户登录时,需要设置新的token
     */
    public async setToken(token: TokenStringGetter) {
        const client = this.client!

        client.close()
        client.setToken(this.trimToken(await token()))
        client.open()
    }

    public fetchMsgInBox(chatId: number, msgId: number) {
        return this.client!.fetchMsgInBox(chatType, chatId, msgId)
    }

    /**
     * 发送消息
     */
    public sendMsg(
        chatType: string,
        chatId: number,
        msgType: string,
        msg: string
    ) {
        this.checkConnected()

        return this.client!.sendMsg(chatType, chatId, msgType, msg, "", {})
    }

    public inputing(chatId: number) {
        this.checkConnected()
        return this.client!.userInput(chatType, chatId)
    }

    /*
     * 查询会话
     */
    public fetchChatMembers(chat_id: number) {
        this.checkConnected()

        return this.client!.fetchChatMembers(chat_id)
    }

    /**
     * 查询消息
     */
    public async queryMsgs(
        chatType: string,
        chatId: number,
        lid = 0,
        rid = 0,
        limit = DefaultMsgPageSize,
        desc = true
    ): Promise<Message[]> {
        this.checkConnected()
        const res = await this.client!.fetchChatMsgs(chatType, chatId, {
            lid,
            rid,
            limit,
            desc,
        })
        return res.args[0]
    }

    private setMessagesRead(chatId: number, msg: Message[]) {
        if (msg.length === 0) return
        return this.setRead(chatId, msg[0].id, msg[msg.length - 1].id)
    }

    /** 查询最后一页消息 */
    public async queryLastPageMsg(
        chatType: string,
        chatId: number,
        limit: number
    ) {
        const data = await this.queryMsgs(chatType, chatId, 0, 0, limit, true)
        this.setMessagesRead(chatId, data)
        return data
    }

    /** 查询上一页消息 */
    public async queryPrevPageMsg(
        chatType: string,
        chatId: number,
        msgId: number,
        limit: number
    ) {
        const data = await this.queryMsgs(
            chatType,
            chatId,
            0,
            msgId,
            limit,
            true
        )
        this.setMessagesRead(chatId, data)
        return data
    }

    /** 查询下一页消息 */
    public async queryNextPageMsg(
        chatType: string,
        chatId: number,
        msgId: number,
        limit: number
    ) {
        const data = await this.queryMsgs(
            chatType,
            chatId,
            msgId,
            0,
            limit,
            false
        )
        this.setMessagesRead(chatId, data)
        return data
    }

    public on(event: "msg", chatId: number, listener: MsgListener): this
    public on(event: "msg", listener: MsgListener): this
    public on(
        event: "chat_notify",
        kind: "chat_change",
        listener: ChatNotifyListener
    ): this
    public on(
        event: "chat_notify",
        kind: string,
        listener: ChatNotifyListener
    ): this
    public on(event: "chat_notify", listener: ChatNotifyListener): this
    public on(event: "status", listener: StatusChangeListener): this
    public on(...args: any[]): this {
        this.eventBus.$on(...this.parseEventListener(...args))
        return this
    }

    public off(event: "msg", chatId: number, listener: MsgListener): this
    public off(event: "msg", listener: MsgListener): this
    public off(
        event: "chat_notify",
        kind: "chat_change",
        listener: ChatNotifyListener
    ): this
    public off(
        event: "chat_notify",
        kind: string,
        listener: ChatNotifyListener
    ): this
    public off(event: "chat_notify", listener: ChatNotifyListener): this
    public off(event: "status", listener: StatusChangeListener): this
    public off(...args: any[]): this {
        this.eventBus.$off(...this.parseEventListener(...args))
        return this
    }

    public once(...args: any[]): this {
        this.eventBus.$once(...this.parseEventListener(...args))
        return this
    }

    public emit(event: string, ...args: any[]): this {
        this.eventBus.$emit(event, ...args)
        return this
    }

    /**
     * 移除会话(用户端/移动端使用)
     */
    public async closeChat(chatId: number) {
        return this.client?.setChat(chatId, { is_remove: true })
    }

    public isConnected() {
        return this.client?.connected
    }

    public setRead(chatId: number, start_msg_id: number, end_msg_id: number) {
        return this.client?.syncReadMsg(chatId, start_msg_id, end_msg_id)
    }

    private parseEventListener(...args: any[]): [string, Function] {
        if (args.length < 2) {
            throw new Error("参数个数不正确")
        }

        const listener: Function = args[args.length - 1]
        return [args.slice(0, -1).join("."), listener]
    }

    private onConnected() {
        // 连接成功后,需要调用pubUserInfo, 否则服务端会认为此连接无效
        this.client!.pubUserInfo("")
        this.debug("xim connected")
    }

    /*
        DISCONNECTED: "DISCONNECTED",
        CONNECTING: "CONNECTING",
        CONNECTED: "CONNECTED",
        CLOSED: "CLOSED", 
     */
    private onStatusChange(status: any, details: any) {
        this.debug("onstatuschange", status, details)
        this.emit(Events.Status, status, details)
        if (status === "DISCONNECTED" || status === "CLOSED") {
            this.hanldeOffline()
        }
    }

    private hanldeOffline() {
        this.debug("开始重连")
        this.reOpen()
    }

    private reOpen() {
        if (this.connectionPending) return
        if (this.paramsForReconnection == null) return
        this.open(
            this.paramsForReconnection.url,
            this.paramsForReconnection.token
        )
    }

    private handleMsg(kind: any, msg: any) {
        this.debug(`收到消息 ${new Date().getTime()}`, kind, msg)

        switch (kind) {
            case "chat":
                this.emit(`msg`, msg)
                this.emit(`msg.${msg.chat_id}`, msg)
                break
            case "chat_notify":
                this.emit(`chat_notify`, msg)
                this.emit(`chat_notify.${msg.msg_type}`, msg)
                break
            default:
                this.emit(kind, msg)
        }
    }

    private checkConnected() {
        if (!this.client!.connected) {
            try {
                this.client?.open()
            } catch (e) {
                console.error("checkConnected", e)
                this.reOpen()
            }
        }
    }

    private debug(message: any, ...params: any[]) {
        ChatLoggerService.logger?.debug(message, params)
    }
}

const ximInstance = new Xim()

export default ximInstance