/**
 * Token 管理器 - 处理声网 Token 的过期和自动刷新
 */

import { type MeetingToken, getMeetingTokenAxios } from '@/api/meeting';
import type { IAgoraRTCClient } from 'agora-rtc-sdk-ng';
import type { Ref } from 'vue';

export interface TokenManagerOptions {
  /** 课程房间 ID */
  channelName: Ref<string>;
  /** 用户短 UID */
  uid: Ref<number>;
  /** Token 即将过期时的回调 */
  onWillExpire?: (willExpireAt: number) => void;
  /** Token 已过期时的回调 */
  onExpired?: () => void;
  /** Token 刷新成功后的回调 */
  onRefreshed?: (newToken: MeetingToken) => void;
  /** 错误回调 */
  onError?: (error: Error) => void;
}

export class TokenManager {
  private currentToken: MeetingToken | null = null;
  private rtcClient: IAgoraRTCClient | null = null;

  private channelName: Ref<string>;
  private uid: Ref<number>;
  private onWillExpire?: (willExpireAt: number) => void;
  private onExpired?: () => void;
  private onRefreshed?: (newToken: MeetingToken) => void;
  private onError?: (error: Error) => void;

  public constructor(options: TokenManagerOptions) {
    this.channelName = options.channelName;
    this.uid = options.uid;
    this.onWillExpire = options.onWillExpire;
    this.onExpired = options.onExpired;
    this.onRefreshed = options.onRefreshed;
    this.onError = options.onError;
  }

  /**
   * 绑定声网客户端并监听 Token 过期事件
   * @param rtcClient - 声网 RTC 客户端
   */
  public bindClient(rtcClient: IAgoraRTCClient): void {
    if (this.rtcClient === rtcClient) {
      return;
    }
    this.unbindClient();
    this.rtcClient = rtcClient;
    this.rtcClient.on('token-privilege-will-expire', this.handleWillExpire);
    this.rtcClient.on('token-privilege-did-expire', this.handleDidExpire);
  }

  /**
   * 解绑声网客户端 Token 过期监听
   */
  public unbindClient(): void {
    if (!this.rtcClient) {
      return;
    }
    this.rtcClient.off('token-privilege-will-expire', this.handleWillExpire);
    this.rtcClient.off('token-privilege-did-expire', this.handleDidExpire);
    this.rtcClient = null;
  }

  /**
   * 设置当前 Token 信息
   * @param token - Token 信息
   */
  public setCurrentToken(token: MeetingToken): void {
    this.currentToken = token;
  }

  /**
   * 刷新 Token
   */
  public async refreshToken(): Promise<void> {
    await this.renewTokenByReason('手动刷新');
  }

  /**
   * 获取当前 Token
   */
  public getCurrentToken(): MeetingToken | null {
    return this.currentToken;
  }

  /**
   * 清理资源
   */
  public destroy(): void {
    this.unbindClient();
    this.currentToken = null;
  }

  /**
   * 监听 Token 即将过期
   */
  private handleWillExpire = async (): Promise<void> => {
    const willExpireAt = this.currentToken?.expiresAt ?? Date.now();
    this.onWillExpire?.(willExpireAt);
    await this.renewTokenByReason('即将过期');
  };

  /**
   * 监听 Token 已过期
   */
  private handleDidExpire = async (): Promise<void> => {
    this.onExpired?.();
    await this.renewTokenByReason('已过期');
  };

  /**
   * 获取最新 Token
   */
  private async fetchToken(): Promise<MeetingToken> {
    const channelName = this.channelName.value;
    const uid = this.uid.value;
    if (!uid || uid <= 0) {
      throw new Error('用户短 UID 无效，无法刷新 Token');
    }
    const tokenData = await getMeetingTokenAxios(channelName, uid);
    return tokenData.data;
  }

  /**
   * 根据原因刷新并更新声网客户端 Token
   * @param reason - 刷新原因
   */
  private async renewTokenByReason(reason: string): Promise<void> {
    try {
      if (!this.rtcClient) {
        throw new Error('声网客户端未初始化，无法更新 Token');
      }
      console.warn(`[Token] Token${reason}，开始刷新...`);
      const newToken = await this.fetchToken();
      await this.rtcClient.renewToken(newToken.rtcToken);
      this.currentToken = newToken;
      this.onRefreshed?.(newToken);
      console.log('[Token] Token 刷新并更新到客户端成功');
    } catch (error) {
      console.error('[Token] Token 刷新失败:', error);
      this.onError?.(error as Error);
      throw error;
    }
  }
}
