import type { Client, Frame, Message } from 'stompjs'
import Stomp from './uni-stomp'
import UniWebSocket from './uni-websocket'
import { useMessage } from '/@/hooks/app/useMessage'
import { API_URL, API_URL_PREFIX } from '/@/utils/net'
import { useUserStoreWithOut } from '@/store/modules/user'

const { createMessage } = useMessage()

// 自定义实现 WebSocket 类
Stomp.WebSocketClass = UniWebSocket

/**
 * WebSocket Stomp 实例
 */
class StompInstance {
    // 连接地址
    private url: string

    // 是否开启调试模式
    private debug: boolean

    // Stomp 实例
    private client: Client

    // 订阅集合
    private subscribes: Recordable

    // 重连延时标记
    private reconnectId: NodeJS.Timeout

    // 重连间隔
    private reconnectInterval: number

    // 检查订阅延时标记
    private checkSubscribeId: NodeJS.Timeout

    // 检查订阅的时间间隔
    private checkSubscribeInterval = 3000

    // 缓存 token
    token: string

    // 缓存用户信息
    info: Recordable

    constructor({
        url,
        debug = false,
        reconnectInterval = 10000,
    }: {
        url: string
        debug?: boolean
        reconnectInterval?: number
    }) {
        // 重连间隔
        this.reconnectInterval = reconnectInterval
        // 是否打印debug信息
        this.debug = debug
        // ws地址
        if (url.indexOf('http://') === 0) {
            this.url = url.replace('http://', 'ws://')
        } else if (url.indexOf('https://') === 0) {
            this.url = url.replace('https://', 'wss://')
        } else {
            this.url = url
        }
        // stomp实例
        this.client = null
        // 重连事件编号
        this.reconnectId = null
        // 订阅集合
        this.subscribes = {}
    }

    /**
     * 创建连接
     */
    async connect(cb?: () => void) {
        // 如已存在连接则不创建
        if (this.client && this.client.connected) {
            return
        }

        // 检查登录是否有效
        const available = await API.auth.valid.request()
        // 从缓存中获取token
        const useStore = useUserStoreWithOut()
        const token = useStore.getToken
        const options = token && available ? { token } : {}

        // 创建 Stomp 实例
        this.client = Stomp.client(this.url) as Client

        // 控制是否打印debug信息
        if (!this.debug) {
            this.client.debug = () => {}
        }

        // 连接
        this.client.connect(
            options,
            (frame: Frame) => {
                // 缓存连接用户信息
                this.token = token
                this.info = JSON.parse(frame.body)

                if (this.debug) {
                    console.log(`[Stomp] connected, user: ${this.info.user}`)
                }

                // 检查消息订阅
                this.loopCheckSubscribe()

                // 订阅全局通知
                this.client.subscribe('/user/topic/notice', (frame) => {
                    if (this.debug) {
                        console.debug('STOMP Notice: ', frame.body)
                    }

                    const body = JSON.parse(frame.body)
                    if (body.status === 400) {
                        createMessage.warn(body.message)
                    } else if ([401, 403].includes(body.status)) {
                        // TODO: 退出登录
                    }

                    // TODO 根据实际情况再行补充
                })

                // 发送登录消息
                if (token && available) {
                    this.send('/app/login', token)
                }

                // 连接成功后触发回调
                cb?.()
            },
            (_) => {
                // 重连
                this.reconnectId = setTimeout(async () => {
                    await this.reconnect()
                }, this.reconnectInterval)
            },
        )
    }

    /**
     * 重新连接
     */
    async reconnect() {
        // 停止重连事件
        if (this.reconnectId) {
            clearTimeout(this.reconnectId)
            this.reconnectId = null
        }
        // 停止订阅状态检查
        if (this.checkSubscribeId) {
            clearTimeout(this.checkSubscribeId)
            this.checkSubscribeId = null
        }

        // 订阅状态置false
        Object.keys(this.subscribes).forEach((key) => {
            this.subscribes[key].subscribed = false
        })

        // 连接
        await this.connect()
    }

    /**
     * 断开连接
     */
    disconnect() {
        // 断开连接
        if (this.client) {
            this.client.disconnect(() => {})
        }
        // 停止重连事件
        if (this.reconnectId) {
            clearTimeout(this.reconnectId)
            this.reconnectId = null
        }
        // 停止订阅状态检查
        if (this.checkSubscribeId) {
            clearTimeout(this.checkSubscribeId)
            this.checkSubscribeId = null
        }
        // 清空所有除订阅缓存
        this.subscribes = {}
    }

    /**
     * 订阅
     * @param {string} destination 主题
     * @param {Function} callback 回调
     */
    subscribe(destination: string, callback: (message: Message) => any) {
        if (this.subscribes[destination] && this.subscribes[destination].subscribed) {
            // 已订阅
        } else if (this.client && this.client.connected) {
            // 已连接：调用订阅，缓存订阅信息
            const subscribe = this.client.subscribe(destination, (res) => callback(res))
            this.subscribes[destination] = { callback, subscribed: true, subscribe }
        } else {
            // 未连接：缓存订阅信息
            this.subscribes[destination] = { callback, subscribed: false }
        }
    }

    /**
     * 取消订阅
     * @param {string} destination 主题
     */
    unsubscribe(destination: string) {
        if (this.subscribes[destination]) {
            // 取消订阅
            this.subscribes[destination].subscribe.unsubscribe()
            // 删除订阅缓存
            delete this.subscribes[destination]
        }
    }

    /**
     * 轮询检查订阅状态
     */
    private loopCheckSubscribe() {
        this.checkSubscribeId = setInterval(() => {
            Object.keys(this.subscribes).forEach((key) => {
                this.subscribe(key, this.subscribes[key].callback)
            })
        }, this.checkSubscribeInterval)
    }

    /**
     * 向服务器发送消息
     * @param {string} destination 主题
     * @param {object | string} message 消息内容
     * @param {object} headers 消息头
     */
    send(destination: string, message: Object | string, headers?: Object) {
        if (this.client) {
            this.client.send(destination, headers, typeof message === 'object' ? JSON.stringify(message) : message)
        }
    }
}

const baseUrl = API_URL + API_URL_PREFIX
const instance = new StompInstance({
    url: `${baseUrl}/stomp/ws`,
    debug: true,
})

export default instance
