import { MQTTLogic } from "../MQTTLogic";
import { CastSessionMessage } from "../message/CastSessionMessage";
import { DataMessage } from "../message/DataMessage";
import { DisplayReportMessage } from "../message/DisplayReportMessage";
import { MsgClientType } from "../message/MsgClientType";
import { MsgType } from "../message/MsgType";
import { WillMessage } from "../message/WillMessage";
import { YMSMessage } from "../message/YMSMessage";
import { IControllerMQTTCallback } from "./IControllerMQTTCallback";
import { Utils } from '../../../util/Utils';
import { AccountBindDeviceMessage } from "../message/AccountBindDeviceMessage";
import { UserHasBeenKickedMessage } from "../message/UserHasBeenKickedMessage";
import { SDKLog } from "../../../util/SDKLog";
import { SessionDestroyReason } from "../message/SessionDestroyReason";

export class ControllerMQTTImpl extends MQTTLogic {
    private readonly MYSELF_DISCONNECT_CALLBACK_GAP_MS: number = 500; // 0.5s
    private readonly callback: IControllerMQTTCallback;
    private accountId: number;
    private isSkipDisconnectCallbackOnce: boolean;
    private lastDisconnectTime: number;

    constructor(clientId: string, callback: IControllerMQTTCallback) {
        super(clientId);
        this.callback = callback;
        this.accountId = -1;
        this.isSkipDisconnectCallbackOnce = false;
        this.lastDisconnectTime = 0;
    }

    // Override
    // 控制端需要覆写实现下父类的connect方法
    // 因为控制端需要处理两种场景：1.先登录账户，再连接display；2.先连接display，再登录账户
    public async connect(displayCode: number): Promise<void> {
        if (this.isConnected) {
            // 此时就是场景1。用户先登录账户，再连接display
            // 必须先断开连接，因为登录账户时connect使用的option不包括will message
            // 所以要重新使用新的带有will message的option连接
            this.isSkipDisconnectCallbackOnce = true;
            this.unsubscribeAccountTopic(this.accountId);
            await super.disconnect(displayCode)
                .then((result) => {
                    this.isConnected = false;
                })
                .catch((error) => {
                    //不关注error
                });
        }
        super.connect(displayCode);
        // 连接成功后，再次订阅上account相关的topic消息
        if (-1 != this.accountId) {
            this.subscribeAccountTopic(this.accountId);
        }
    }

    // Override
    // 控制端需要覆写实现下父类的disconnect方法
    // 因为控制端需要处理: 同时处于登录&连接display状态时，用户断开display后，如何处理账号topic消息的监听
    public async disconnect(displayCode: number): Promise<string> {
        // 用户断开display时，原逻辑是彻底断开mqtt连接，但此时用户还处于登录状态
        return new Promise((resolve, reject) => {
          super.disconnect(displayCode)
            .then((result) => {
              resolve(result);
            })
            .catch(reject);
          // 有必要判断此时是否订阅了账号topic消息，如果订阅了，则断开后需要重新连接上mqtt服务
          if (-1 != this.accountId) {
            // 说明此时用户还处于登录状态,重新连接上mqtt服务订阅上账号相关的topic消息
              this.subscribeAccountTopic(this.accountId);
          }
        });
      }

    /**
     * 控制端单独增加的公开方法，用于控制端成功完成账号登录后，监听该账号topic下消息
     * @param accountId 登录后拿到的账号唯一ID
     */
    public subscribeAccountTopic(accountId: number): void {
        this.accountId = accountId;
        if (!this.isConnected) {
            super.connectWithOptions(super.createMqttOptions());
        }
        let account: string = Utils.getTopicPrefix(MsgType.ACCOUNT) + accountId;
        let bindDevice: string = Utils.getTopicPrefix(MsgType.BIND_DEVICE) + accountId;
        let topics: string[] = [account, bindDevice];
        super.getClient()?.subscribe(topics);
    }

    /**
     * 控制端单独增加的公开方法，用于控制端成功退出账号或注销账号后，解除对指定账号的topic消息监听
     * @param accountId 登录后拿到的账号唯一ID
     */
    public unsubscribeAccountTopic(accountId: number): void {
        this.accountId = -1;
        if (!this.isConnected) {
            return;
        }
        let account: string = Utils.getTopicPrefix(MsgType.ACCOUNT) + accountId;
        let bindDevice: string = Utils.getTopicPrefix(MsgType.BIND_DEVICE) + accountId;
        let topics: string[] = [account, bindDevice];
        super.getClient()?.unsubscribe(topics);
    }

    protected isMessageFromSelf(message: YMSMessage): boolean {
        return MsgClientType.CONTROLLER == message.sender;
    }

    protected isMessageForMe(message: YMSMessage): boolean {
        return (MsgClientType.ANY == message.receiver
                || MsgClientType.CONTROLLER == message.receiver);
    }

    protected createMySelfWill(displayCode: number, clientId: string): WillMessage {
        let ret: WillMessage = new WillMessage(MsgClientType.CONTROLLER, MsgClientType.ANY);
        ret.senderClientId = clientId;
        ret.timestamp = Date.now();
        return ret;
    }
    protected subscribeInterestTopic(displayCode: number): void {
        let will: string = Utils.getTopicPrefix(MsgType.WILL) + displayCode;
        let report: string = Utils.getTopicPrefix(MsgType.REPORT) + displayCode;
        let cast: string = Utils.getTopicPrefix(MsgType.CAST) + displayCode;
        let topics: string[] = [will, report, cast];
        super.getClient()?.subscribe(topics);
    }

    protected unsubscribeAllTopics(displayCode: number): void {
        let will: string = Utils.getTopicPrefix(MsgType.WILL) + displayCode;
        let report: string = Utils.getTopicPrefix(MsgType.REPORT) + displayCode;
        let cast: string = Utils.getTopicPrefix(MsgType.CAST) + displayCode;
        let topics: string[] = [will, report, cast];
        super.getClient()?.unsubscribe(topics);
    }

    protected onWillMessageArrived(message: WillMessage): void {
        // must be display or server died
        if (MsgClientType.DISPLAY == message.sender) {
            this.callback.onDisplayDied();
        } else if (MsgClientType.SERVER == message.sender) {
            this.callback.onServerDied();
        }
    }

    protected onDisplayReportMessageArrived(message: DisplayReportMessage): void {
        this.callback.onDisplayReport(message.displayReport);
    }

    protected onCastSessionMessageArrived(message: CastSessionMessage): void {
        this.callback.onCastSession(message.castSession, message.action, message.mediaPayload, message.destroyReason?? SessionDestroyReason.NONE);
    }

    protected onDataMessageArrived(message: DataMessage): void {
        // we don't care about data msg
    }

    protected onUserHasBeenKickedMessageArrived(message: UserHasBeenKickedMessage): void {
        this.callback.onUserHasBeenKicked(message.user);
    }

    protected onAccountBindDeviceMessageArrived(message: AccountBindDeviceMessage): void {
        this.callback.onAccountBindDevice(message.accountBindDevice, message.bindDeviceAction);
    }

    protected onConnectionLost(cause: string): void {
        let now = Date.now();
        if (now - this.lastDisconnectTime <= this.MYSELF_DISCONNECT_CALLBACK_GAP_MS) {
            // 自身断开连接回调做一个时间防抖，一定时长内连续触发我们都予以跳过
            // 因为实操中发现前端使用的mtqq库会多次回调connectionLost
            SDKLog.d(this.TAG, "一定时长内连续触发connectionLost回调,跳过");
            return;
        }
        this.lastDisconnectTime = now;
        if (this.isSkipDisconnectCallbackOnce) {
            // 此时，场景是用户先登录连接了mqtt，再connect display
            // 我们在connect时会先断开mqtt再重连，如果这次断开触发回调会导致上层不符合预期
            SDKLog.d(this.TAG, "先登录再connect display,sdk主动触发一次重连,跳过这次回调");
            this.isSkipDisconnectCallbackOnce = false;
            return;
        }
        this.callback.onMySelfDisconnected(cause);
    }    

}