import mqtt from 'mqtt'
import type { MqttClient, IClientOptions, IClientPublishOptions } from 'mqtt'
import type { QoS } from 'mqtt-packet'
// import { initMqttKeyApi } from '@/api/modules/user'
/**
 * 消息处理器类型定义
 * 接收主题名称和消息内容两个参数
 */
type MessageHandler = (topic: string, message: string) => void

/**
 * 主题-回调映射对象类型
 */
type TopicCallbackMap = Record<string, MessageHandler>

/**
 * MQTT 客户端封装类 - 支持多主题订阅管理
 */
class MQTTClient {
  /** MQTT 连接实例 */
  private client: MqttClient | null = null

  /** 连接状态标志 */
  private isConnected: boolean = false

  /** 使用 Set 存储已订阅的主题，自动去重，便于重连时恢复订阅 */
  private subscribedTopics: Set<string> = new Set()

  /** 使用 Map 存储主题与回调函数的映射关系，实现精准的消息分发 */
  private messageHandlers: Map<string, MessageHandler> = new Map()

  /**
   * 构造函数 - 初始化客户端状态
   */
  constructor() {
    // 状态已在属性声明时初始化
  }

  /**
   * 建立 MQTT 连接
   * @param url - 服务器地址，如 'wss://broker.emqx.io:8084/mqtt'
   * @param options - 连接配置选项
   * @returns 客户端实例，便于链式调用或外部访问
   */
  connect(url: string, options: Partial<IClientOptions> = {}): MqttClient {
    // 合并默认配置与用户传入的配置
    const defaultOptions: IClientOptions = {
      clean: true, // true: 清除会话，false: 保留会话（离线消息）
      connectTimeout: 4000, // 连接超时时间（毫秒）
      reconnectPeriod: 5000, // 自动重连间隔（毫秒）
      // 生成唯一客户端ID，格式：vue3-client-8位随机十六进制
      clientId: 'vue3-client-' + Math.random().toString(16).substr(2, 8),
      ...options, // 展开用户自定义选项，覆盖默认值
    }

    // 创建 MQTT 连接实例
    this.client = mqtt.connect(url, defaultOptions)

    /**
     * 连接成功事件
     * 触发时机：首次连接成功或断线重连成功
     */
    this.client.on('connect', () => {
      console.log('✅ MQTT 连接成功')
      this.isConnected = true

      // 重连场景：自动恢复之前的所有订阅
      // 避免网络波动导致订阅丢失
      this.resubscribeAll()
    })

    /**
     * 消息到达事件
     * 触发时机：收到已订阅主题的任何消息
     */
    this.client.on('message', (topic: string, message: Uint8Array) => {
      // 从映射表中查找该主题对应的处理函数
      const handler = this.messageHandlers.get(topic)

      if (handler) {
        // 将 Buffer 转换为字符串后传递给回调函数
        handler(topic, message.toString())
      } else {
        // 未找到处理函数时的降级处理
        console.warn(`收到未处理的主题消息: ${topic}`, message.toString())
      }
    })

    /**
     * 错误事件
     * 触发时机：连接过程中发生错误（认证失败、网络异常等）
     */
    this.client.on('error', (error: Error) => {
      console.error('❌ MQTT 连接错误:', error)
      this.isConnected = false
    })

    /**
     * 重连事件
     * 触发时机：连接断开，客户端尝试自动重连时
     */
    this.client.on('reconnect',async () => {
      // const foreignDeviceNo = sessionStorage.getItem('foreign_device_no') || ''
      // const mqttKey = await initMqttKeyApi({ device_no: foreignDeviceNo })
      // console.log("initMqttKey result:", mqttKey)
      console.log('🔄 MQTT 正在重连...')
    })

    /**
     * 连接关闭事件
     * 触发时机：连接被关闭（网络断开、手动断开、服务器踢出）
     */
    this.client.on('close',  () => {
      console.log('🔌 MQTT 连接关闭')
      this.isConnected = false
       
    })

    // 返回客户端实例，便于链式调用或外部访问
    return this.client
  }

  /**
   * 订阅一个或多个主题
   * 支持单主题字符串或主题数组，实现批量订阅
   *
   * @param topics - 单个主题或主题数组
   * @param qos - 服务质量等级 (0:最多一次, 1:至少一次, 2:恰好一次)
   * @returns 返回 Promise，resolve 时传入成功订阅的主题列表
   *
   * 使用示例：
   *   subscribe('sensor/temp')                    // 订阅单个主题
   *   subscribe(['sensor/temp', 'sensor/hum'])    // 批量订阅两个主题
   */
  subscribe(topics: string | string[], qos: QoS = 0): Promise<string[]> {
    // 检查连接状态，未连接时拒绝订阅请求
    if (!this.isConnected || !this.client) {
      console.warn('MQTT 未连接，无法订阅')
      return Promise.reject('MQTT 未连接')
    }

    // 统一转换为数组处理：如果是字符串则包装成数组，数组则直接使用
    const topicArray: string[] = Array.isArray(topics) ? topics : [topics]

    // 返回 Promise 便于使用 async/await 等待订阅完成
    return new Promise((resolve, reject) => {
      /**
       * 调用 mqtt.js 的 subscribe 方法
       * 第一个参数：主题或主题数组
       * 第二个参数：选项对象，这里设置 QoS 等级
       * 第三个参数：回调函数，err 为 null 表示成功
       */
      this.client!.subscribe(topicArray, { qos }, (err?: Error | null) => {
        if (err) {
          // 订阅失败（权限不足、主题格式错误等）
          console.error('订阅失败:', err)
          reject(err)
        } else {
          // 订阅成功，将每个主题加入追踪集合
          topicArray.forEach((topic: string) => {
            this.subscribedTopics.add(topic)
            console.log(`✅ 已订阅主题: ${topic} (QoS: ${qos})`)
          })
          // resolve 返回成功订阅的主题数组
          resolve(topicArray)
        }
      })
    })
  }

  /**
   * 为单个主题注册消息处理函数
   * 实现主题与回调的一对一绑定
   *
   * @param topic - 主题名称
   * @param callback - 回调函数，接收 (topic, message) 两个参数
   *
   * 使用示例：
   *   onTopicMessage('sensor/temp', (topic, msg) => {
   *     console.log('温度:', JSON.parse(msg).value)
   *   })
   */
  onTopicMessage(topic: string, callback: MessageHandler): void {
    // 将主题与回调函数存入 Map
    this.messageHandlers.set(topic, callback)
  }

  /**
   * 批量注册主题处理函数
   * 通过对象映射一次性配置多个主题的处理逻辑
   *
   * @param topicCallbackMap - 主题-回调映射对象
   *   key: 主题名称
   *   value: 处理该主题的回调函数
   *
   * 使用示例：
   *   onTopicMessages({
   *     'sensor/temp': (t, m) => handleTemp(m),
   *     'sensor/hum': (t, m) => handleHum(m),
   *     'alert/urgent': (t, m) => showAlert(m)
   *   })
   */
  onTopicMessages(topicCallbackMap: TopicCallbackMap): void {
    // 遍历对象的所有键值对
    Object.entries(topicCallbackMap).forEach(([topic, callback]) => {
      // 逐个存入 messageHandlers Map
      this.messageHandlers.set(topic, callback)
    })
  }

  /**
   * 重连时恢复所有订阅
   * 私有方法，在 connect 事件触发时自动调用
   * 确保网络波动断线重连后，订阅关系自动恢复
   */
  private resubscribeAll(): void {
    // 检查是否有历史订阅记录
    if (this.subscribedTopics.size > 0) {
      // 将 Set 转换为数组
      const topics: string[] = Array.from(this.subscribedTopics)
      console.log('🔄 重连后恢复订阅:', topics)

      // 重新订阅所有主题
      this.subscribe(topics)
    }
  }

  /**
   * 取消订阅一个或多个主题
   * 清理订阅关系，释放资源
   *
   * @param topics - 要取消订阅的主题
   * @returns 操作完成的 Promise
   */
  unsubscribe(topics: string | string[]): Promise<void> {
    // 统一处理为数组
    const topicArray: string[] = Array.isArray(topics) ? topics : [topics]

    return new Promise((resolve, reject) => {
      if (!this.client) {
        reject(new Error('MQTT 客户端未初始化'))
        return
      }

      this.client.unsubscribe(topicArray, (err?: Error) => {
        if (err) {
          reject(err)
        } else {
          // 清理内部状态：从追踪集合和处理器映射中移除
          topicArray.forEach((topic: string) => {
            this.subscribedTopics.delete(topic)
            this.messageHandlers.delete(topic)
            console.log(`❌ 已取消订阅: ${topic}`)
          })
          resolve()
        }
      })
    })
  }

  /**
   * 向指定主题发布消息
   *
   * @param topic - 目标主题
   * @param message - 消息内容，对象会自动序列化为 JSON
   * @param qos - 服务质量等级
   * @returns 是否发送成功
   */
  publish(topic: string, message: string | object, qos: number = 0): boolean {
    if (!this.isConnected || !this.client) {
      console.warn('MQTT 未连接，无法发送消息')
      return false
    }

    // 自动序列化对象类型消息
    const payload: string = typeof message === 'string' ? message : JSON.stringify(message)

    // 调用 mqtt.js 的 publish 方法
    this.client.publish(topic, payload, { qos } as IClientPublishOptions)
    return true
  }

  /**
   * 断开 MQTT 连接并清理资源
   * 应用退出或组件销毁时调用，防止内存泄漏
   */
  disconnect(): void {
    // 清空所有内部状态
    this.subscribedTopics.clear()
    this.messageHandlers.clear()

    // 关闭连接
    this.client?.end()

    // 重置状态标志
    this.isConnected = false
    console.log('🔌 MQTT 已断开连接')
  }

  /**
   * 获取当前连接状态
   * @returns 是否已连接
   */
  getConnected(): boolean {
    return this.isConnected
  }

  /**
   * 获取已订阅的主题列表
   * @returns 已订阅主题数组
   */
  getSubscribedTopics(): string[] {
    return Array.from(this.subscribedTopics)
  }
}

// 导出实例
export { MQTTClient }
export type { MessageHandler, TopicCallbackMap }
