import { v4 } from 'uuid'
import { Package40, Package50 } from './package';
import moment from 'moment'

/**
 * 连接状态
 */
enum ConnectionState {
    Unconnected, // 未连接
    Connecting, // 连接中
    Connected, // 已连接
}

/** 重连状态 */
export enum ReconnectChangeState {
    /** 开始重连 */
    BeginReconnect,
    /** 尝试重连失败，将在下一个周期后再次尝试重连 */
    FailAndWaitPeriod,
    /** 重连成功，将进行业务操作 */
    ReconnectSuccessed,
    /** 重连成功后业务操作失败并将再次重试，由业务模块发起 */
    LoginFail,
    /** 重连成功后业务操作成功（包括交易服务的账户登录状态更新或行情服务的行情订阅等 */
    Logined,
}

export type beatTimer = 'quote' | 'trade';

/**
 * 回调类
 */
export interface Callback {
    /** 成功回调 */
    onSuccess?<T extends Package40 | Package50>(res: T): void;
    /** 失败回调 */
    onFail?(err: Error): void;
}

/** 信息发送异步任务类 */
class AsyncTask {
    /** 5.0报文直接为流水号；4.0报文为流水号+"_"+大类号（由于服务端行情推送会使用流水号自增） */
    key: string;
    /** 返回功能码，主要用于过滤账户服务的错传包 */
    rspFunCode?: number;
    /** 超时标识 */
    timeOut: number;
    /** 回调 */
    callback: Callback;

    constructor(key: string, timeOut: number, callback: Callback, rspFunCode?: number) {
        this.key = key;
        this.rspFunCode = rspFunCode;
        this.timeOut = timeOut;
        this.callback = callback;
    }
}

/** MTP2.0 长链通信类 */
export class MTP2WebSocket<T extends Package40 | Package50> {
    /** 初始默认重连间隔时长 */
    private defaultReconnectInterval = 5 * 1000;
    /** 服务端地址 */
    host;
    /** 当前连接状态 */
    connState: keyof typeof ConnectionState = 'Unconnected';
    /** 重连次数 */
    reconnectCount = 0;
    /** 重连间隔时长，默认为5秒 */
    reconnectInterval = this.defaultReconnectInterval;

    /** 连接成功的回调 */
    onConnected?: (obj: MTP2WebSocket<T>) => void;
    /** 连接断开的回调 */
    onClosed?: (obj: MTP2WebSocket<T>) => void;
    /** 连接发生错误的回调 */
    onError?: (obj: MTP2WebSocket<T>, err: Error) => void;
    /** 接收到推送类报文的回调 */
    onReceivePush?: (obj: MTP2WebSocket<T>, data: T) => void;
    /** 重连状态发生改变时的回调 */
    onReconnectChangeState?: (obj: MTP2WebSocket<T>, state: ReconnectChangeState) => void;

    /** 实例唯一标识 */
    private uuid = '';
    /** 报文类型，0 - 4.0行情报文，1 - 5.0交易报文 */
    private packageType;
    /** WebSocket 对象 */
    private socket?: WebSocket;
    /** 连接准备完成 */
    private readyState?: Promise<MTP2WebSocket<T>>;
    /** 当前流水号 */
    private currentSerial = 1;
    /** 默认请求超时时长（秒） */
    private timeOutInterval = 30 * 1000;
    /** 信息发送异步建值对 */
    private asyncTaskMap: Map<string, AsyncTask> = new Map();
    /** 段包 */
    private cache?: number[];
    /** 当前处理的段包报文的原长度 */
    private cachePackageLength = 0;

    /** 心跳定时器 */
    private beatTimer = 0;
    /** 心跳发送时间 */
    private beatTimerId = '';
    /** 重连定时器 */
    private reconnectTimer = 0;
    /** 超时定时器 */
    private timeoutTimer = 0;
    /** 心跳间隔时间，默认为30秒 */
    private beatInterval = 30 * 1000;
    /** 心跳回复超时时间，默认为15秒 */
    private beatTimeoutInterval = 15 * 1000;

    /**
     * 构造函数
     * @param packageType 报文类型，0 - 4.0行情报文，1 - 5.0交易报文
     */
    constructor(packageType: beatTimer, host?: string) {
        this.packageType = packageType;
        this.host = host || '';
    }

    /**
     * 连接服务端
     * @param host 服务端地址
     */
    conn(host?: string) {
        if (!this.readyState) {
            clearTimeout(this.reconnectTimer);
            this.stopBeatTimer();
            this.host = host || this.host;
            this.connState = 'Connecting';
            console.log(this.packageType, this.host, '正在连接');

            this.readyState = new Promise((resolve, reject) => {
                const uuid = v4();
                this.uuid = uuid;
                this.socket = new WebSocket(this.host);

                // 连接成功
                this.socket.onopen = () => {
                    console.log(this.packageType, this.host, '连接成功');
                    this.connState = 'Connected';
                    this.startBeatTime();
                    this.onConnected && this.onConnected(this);
                    resolve(this);
                }
                // 连接断开(CLOSING有可能延迟)
                this.socket.onclose = () => {
                    // 判断是否当前实例
                    // 如果连接断开后不等待 onclose 响应，由于 onclose 延迟的原因可能会在创建新的 ws 实例后触发，导致刚创建的实例被断开进入重连机制
                    if (this.uuid === uuid) {
                        console.warn(this.packageType, this.host, '连接已断开');
                        this.reset();
                        this.reconnect();
                    }
                }
                // 连接发生错误
                this.socket.onerror = () => {
                    const message = this.host + '连接发生错误';
                    this.onError && this.onError(this, new Error(message));
                    this.callAllAsyncTaskOnReconnecting();  // 回调当前所有发送信息错误块
                    reject(message);
                }
                // 接收数据
                this.socket.onmessage = (e) => {
                    // 接收数据
                    new Response(e.data).arrayBuffer().then((res) => {
                        this.disposeReceiveDatas(new Uint8Array(res));
                    })
                }
            })
        }
        return this.readyState;
    }

    /**
     * 主动断开连接，断开后不会自动重连
     */
    close() {
        clearTimeout(this.reconnectTimer);
        this.stopBeatTimer();
        this.uuid = v4();
        this.reconnectCount = 0;
        this.reconnectInterval = this.defaultReconnectInterval;

        if (this.socket) {
            this.socket.close();
            console.warn(this.packageType, this.host, '主动断开');
        }

        this.reset(); // 不等待 ws.onclose 响应强制重置实例
    }

    /**
     * 重置实例
     */
    private reset() {
        this.socket = undefined;
        this.readyState = undefined;
        this.connState = 'Unconnected';
        this.onClosed && this.onClosed(this);
    }

    /**
     * 启动心跳请求包发送Timer
     * 行情与交易是不同的链路，需要启动不同的定时器发送心跳
     */
    startBeatTime() {
        this.beatTimer = window.setTimeout(() => {
            this.beatTimerId = moment(new Date()).format('HH:mm:ss');
            console.log(this.packageType, '发送心跳', this.beatTimerId);
            // 发送心跳
            switch (this.packageType) {
                case 'quote': {
                    // 4.0
                    this.send40(new Package40([0x12, undefined], undefined), -1, undefined);
                    break;
                }
                case 'trade': {
                    // 5.0
                    this.send50(new Package50([0, undefined]), undefined);
                    break;
                }
            }
            // 如果已经超过或心跳超时时长没有收到心跳回复，则认为网络已经异常，进行断网重连
            // WARNING: - 注意在正常断网时停止心跳逻辑
            this.timeoutTimer = window.setTimeout(() => {
                console.warn(this.packageType, '心跳超时', this.beatTimerId);
                this.reconnect();
            }, this.beatTimeoutInterval)
        }, this.beatInterval)
    }

    /**
     * 停止心跳请求包发送Timer
     */
    stopBeatTimer() {
        clearTimeout(this.timeoutTimer);
        clearTimeout(this.beatTimer);
    }

    /**
     * 发送报文
     * @param p 目标报文
     * @param rsp 回复号。P为Package40对象时为回复大类号，同时必传；P为Package50对象为回复功能码，可不传。
     * @param callback 回调
     */
    send(p: T, rsp?: number, callback?: Callback) {
        if (this.readyState) {
            this.readyState.then(() => {
                if (p instanceof Package40) {
                    // 4.0报文
                    if (!rsp && callback && callback.onFail) {
                        callback.onFail(new Error('缺少rsp'));
                        return;
                    }
                    this.send40(p, rsp!, callback);
                } else {
                    // 5.0报文
                    this.send50(p as Package50, rsp, callback);
                }
            })
        } else {
            if (callback && callback.onFail) callback.onFail(new Error('连接状态错误'));
        }
    }

    /**
     * 发送4.0报文
     * @param p 待发送报文
     * @param rspMainClassNumber 回复大类号
     * @param callback 回调
     */
    private send40(p: Package40, rspMainClassNumber: number, callback?: Callback) {
        // 设置流水号
        p.serialNumber = this.currentSerial;
        this.currentSerial++;

        // 判断是否需要异步回调
        if (callback) {
            const key = p.serialNumber + '_' + rspMainClassNumber;
            const asyncTask = new AsyncTask(
                key,
                window.setTimeout(() => this.asyncTimeOut(key), this.timeOutInterval),
                callback,
                rspMainClassNumber
            );
            this.asyncTaskMap.set(key, asyncTask);
        }
        // 发送信息
        const data = p.data();
        this.socket?.send(data);
    }

    /**
     * 发送5.0报文
     * @param p 待发送报文
     * @param rspFunCode 回复功能码
     * @param callback 回调
     */
    private send50(p: Package50, rspFunCode?: number, callback?: Callback) {
        // 设置流水号
        p.serialNumber = this.currentSerial;
        this.currentSerial++;

        // 判断是否需要异步回调
        if (callback) {
            const key = p.serialNumber.toString();
            const asyncTask = new AsyncTask(
                key,
                window.setTimeout(() => this.asyncTimeOut(key), this.timeOutInterval),
                callback,
                rspFunCode
            );
            this.asyncTaskMap.set(key, asyncTask);
        }
        // 发送信息
        this.socket?.send(p.data());
    }

    /**
     * 异步任务超时
     * @param key key
     */
    private asyncTimeOut(key: string) {
        // 获取对应异步任务对象
        const asyncTask = this.asyncTaskMap.get(key);
        if (asyncTask) {
            if (asyncTask.callback && asyncTask.callback.onFail) asyncTask.callback.onFail(new Error('业务超时'));
        }
        this.asyncTaskMap.delete(key);
    }

    /**
     * 处理接收数据
     * @param bytes 接收的数据
     */
    private disposeReceiveDatas(bytes: Uint8Array) {
        for (let i = 0; i < bytes.length; i++) {
            const byte = bytes[i];

            if (!this.cache) {
                // 新报文
                if (byte !== 0xff) {
                    // 接收到首字节不是0xFF的错误数据
                    return;
                }
                this.cache = [];
                this.cache[this.cache.length] = byte;
            } else {
                // 追加数据
                this.cache[this.cache.length] = byte;
                // 取报文长度
                if (this.cache.length === 5) {
                    if (this.packageType === 'quote') {
                        // 4.0报文
                        this.cachePackageLength = new DataView(new Uint8Array(new Uint8Array(this.cache).subarray(1, 5)).buffer).getUint32(0, false);
                        if (this.cachePackageLength > 65535) {
                            // 接收到长度超过65535的行情包
                            this.cache = undefined;
                            this.cachePackageLength = 0;
                            return;
                        }
                    } else {
                        // 5.0报文
                        const sub = new Uint8Array(this.cache).subarray(1, 5);
                        const dataView = new DataView(new Uint8Array(sub).buffer); // 注意这里要new一个新的Uint8Array，直接buffer取到的还是原数组的buffer

                        this.cachePackageLength = dataView.getUint32(0, true);
                    }
                }
                // 判断是否已经到包尾
                if (this.cache.length === this.cachePackageLength) {
                    if (byte !== 0x0) {
                        // 接收到尾字节不是0x0的错误数据包
                        this.cache = undefined;
                        this.cachePackageLength = 0;
                        return;
                    }
                    if (this.packageType === 'quote') {
                        // 4.0报文
                        this.disposePackage40(new Uint8Array(this.cache));
                    } else {
                        // 5.0报文
                        this.disposePackage50(new Uint8Array(this.cache));
                    }
                    this.cache = undefined;
                    this.cachePackageLength = 0;
                }
            }
        }
    }

    /**
     * 处理完整的4.0报文
     * @param bytes 4.0报文字段流
     */
    private disposePackage40(bytes: Uint8Array) {
        const p = new Package40(undefined, bytes);
        if (p.packageLength === 0) {
            // 报文装箱失败
            return;
        }

        if (p.mainClassNumber === 0x12 || p.mainClassNumber === 0x41 || p.mainClassNumber === 0x42) {
            // 推送类报文, 0x12 - 心跳, 0x41 - 实时行情推送, 0x42 - 控制信号
            if (this.onReceivePush) this.onReceivePush(this, <T>p);

            // 收到心跳
            if (p.mainClassNumber === 0x12) {
                console.log(this.packageType, this.host, '收到心跳回复!');
                this.stopBeatTimer();
                this.startBeatTime();
            }
        } else {
            // 非推送类报文
            const key = p.serialNumber + '_' + p.mainClassNumber;
            const asyncTask = this.asyncTaskMap.get(key);
            if (asyncTask) {
                clearTimeout(asyncTask.timeOut);

                if (asyncTask.callback && asyncTask.callback.onSuccess) asyncTask.callback.onSuccess(p);
                this.asyncTaskMap.delete(key);
            }
        }
    }

    /**
     * 处理完整的5.0报文
     * @param bytes 5.0报文字段流
     */
    private disposePackage50(bytes: Uint8Array) {
        const p = new Package50(undefined, bytes);
        if (p.packageLength === 0) {
            // 报文装箱失败
            return;
        }

        if (p.funCode === 0 || p.serialNumber === 0) {
            // 推送类报文
            if (this.onReceivePush) this.onReceivePush(this, <T>p);

            // 收到心跳
            if (p.funCode === 0) {
                console.log(this.packageType, this.host, '收到心跳回复!');
                this.stopBeatTimer();
                this.startBeatTime();
            }
        } else {
            // 非推送类报文
            const key = p.serialNumber.toString();
            const asyncTask = this.asyncTaskMap.get(key);
            if (asyncTask) {
                // 判断是否需要检验回复功能码
                if (asyncTask.rspFunCode && asyncTask.rspFunCode !== p.funCode) {
                    return;
                }
                clearTimeout(asyncTask.timeOut);
                if (asyncTask.callback && asyncTask.callback.onSuccess) asyncTask.callback.onSuccess(p);
                this.asyncTaskMap.delete(key);
            }
        }
    }

    /**
     * 断网重连方法，在重连尝试失败时会递归调用自己
     */
    private reconnect() {
        this.stopBeatTimer();
        if (this.connState !== 'Connecting') {
            this.reconnectCount++;
            console.log(this.packageType, this.host, `${this.reconnectInterval / 1000}秒后将进行第${this.reconnectCount}次重连`);

            this.reconnectTimer = window.setTimeout(() => {
                this.readyState = undefined;
                this.onReconnectChangeState && this.onReconnectChangeState(this, ReconnectChangeState.BeginReconnect);

                this.conn().then(() => {
                    // 重连成功
                    this.reconnectCount = 0;
                    this.reconnectInterval = this.defaultReconnectInterval;
                    this.onReconnectChangeState && this.onReconnectChangeState(this, ReconnectChangeState.ReconnectSuccessed);
                }).catch(() => {
                    if (this.reconnectCount) {
                        console.warn(this.packageType, this.host, `第${this.reconnectCount}次重连失败`);
                        this.reconnectInterval += this.defaultReconnectInterval; // 重连间隔时间每次递增5秒
                        this.onReconnectChangeState && this.onReconnectChangeState(this, ReconnectChangeState.FailAndWaitPeriod);
                    }
                })
            }, this.reconnectInterval);
        }
    }

    /**
     * 在断网重连时调用所有的错误回调
     */
    private callAllAsyncTaskOnReconnecting() {
        if (this.asyncTaskMap && this.asyncTaskMap.size > 0) {
            for (const key in this.asyncTaskMap) {
                const asyncTask = this.asyncTaskMap.get(key);
                if (asyncTask && asyncTask.callback && asyncTask.callback.onFail) {
                    asyncTask.callback.onFail(new Error('发生断网重连'));
                    clearTimeout(asyncTask.timeOut);
                }
            }
            this.asyncTaskMap.clear();
        }
    }
}
