import { Buffer } from 'buffer'

import { COMMAND_TYPES, EVENT_TYPES } from '../protocol/Constants'
import {
  type BindingResponse,
  type DeleteFileResponse,
  type DeviceInfo,
  type PrepareTransferResponse,
  type UnBindResponse,
  type VersionInfo,
} from '../protocol/types'
import { BleProtocolService } from './BleProtocolService'

export class DeviceInfoService {
  private protocol = BleProtocolService.getInstance()
  private static instance: DeviceInfoService
  private listeners: Map<string, Set<Function>> = new Map()

  private constructor() {
    this.protocol.addListener(EVENT_TYPES.DEVICE_INFO.code, this.onDeviceInfo)
    this.protocol.addListener(EVENT_TYPES.VERSION_INFO.code, this.onVersionInfo)
    this.protocol.addListener(EVENT_TYPES.BIND_DEVICE.code, this.onBindDevice)
    this.protocol.addListener(EVENT_TYPES.UNBIND_DEVICE.code, this.onUnBindDevice)
    this.protocol.addListener(EVENT_TYPES.DELETE_FILE.code, this.onDeleteFile)
    this.protocol.addListener(EVENT_TYPES.PREPARE_TRANSFER.code, this.onPrepareTransfer)
  }

  private onDeviceInfo = (data: ArrayBuffer, deviceId: string) => {
    try {
      const json = this.prase<DeviceInfo>(data)
      this.emit(EVENT_TYPES.DEVICE_INFO.name, json)
    } catch (e) {
      console.error('Failed to parse device info', e)
    }
  }

  private onVersionInfo = (data: ArrayBuffer, deviceId: string) => {
    try {
      const json = this.prase<VersionInfo>(data)
      this.emit(EVENT_TYPES.VERSION_INFO.name, json)
    } catch (e) {
      console.error('Failed to parse activation status', e)
    }
  }

  private onBindDevice = (data: ArrayBuffer, deviceId: string) => {
    try {
      const json = this.prase<BindingResponse>(data)
      this.emit(EVENT_TYPES.BIND_DEVICE.name, json)
    } catch (e) {
      console.error('Failed to parse activation status', e)
    }
  }

  private onUnBindDevice = (data: ArrayBuffer, deviceId: string) => {
    try {
      const json = this.prase<UnBindResponse>(data)
      this.emit(EVENT_TYPES.UNBIND_DEVICE.name, json)
    } catch (e) {
      console.error('Failed to parse activation status', e)
    }
  }

  private onDeleteFile = (data: ArrayBuffer, deviceId: string) => {
    try {
      const json = this.prase<DeleteFileResponse>(data)
      this.emit(EVENT_TYPES.DELETE_FILE.name, json)
    } catch (e) {
      console.error('Failed to parse delete file response', e)
    }
  }

  private onPrepareTransfer = (data: ArrayBuffer, deviceId: string) => {
    try {
      const json = this.prase<PrepareTransferResponse>(data)
      this.emit(EVENT_TYPES.PREPARE_TRANSFER.name, json)
    } catch (e) {
      console.error('Failed to parse prepare transfer response', e)
    }
  }

  public static getInstance(): DeviceInfoService {
    if (!DeviceInfoService.instance) {
      DeviceInfoService.instance = new DeviceInfoService()
    }
    return DeviceInfoService.instance
  }

  private prase<T>(data: ArrayBuffer): T {
    const str = Buffer.from(data).toString('utf-8')
    const cleanStr = str.replace(/\0/g, '')
    return JSON.parse(cleanStr) as T
  }

  public addListener(event: string, cb: Function) {
    if (!this.listeners.has(event)) this.listeners.set(event, new Set())
    this.listeners.get(event)!.add(cb)
  }

  public removeListener(event: string, cb: Function) {
    if (this.listeners.has(event)) {
      this.listeners.get(event)!.delete(cb)
    }
  }

  public removeAllListeners() {
    this.listeners.clear()
    console.debug('All DeviceInfoService listeners cleared')
  }

  private emit(event: string, data: any) {
    this.listeners.get(event)?.forEach((cb) => cb(data))
  }

  public async getDeviceInfo(deviceId: string) {
    await this.protocol.send(deviceId, COMMAND_TYPES.GET_DEVICE_INFO, { type: COMMAND_TYPES.GET_DEVICE_INFO })
  }

  public async getDeviceVersion(deviceId: string) {
    await this.protocol.send(deviceId, COMMAND_TYPES.GET_DEVICE_VERSION, { type: COMMAND_TYPES.GET_DEVICE_VERSION })
  }

  public async bindDevice(deviceId: string, userId: string) {
    await this.protocol.send(deviceId, COMMAND_TYPES.BIND_DEVICE, { type: COMMAND_TYPES.BIND_DEVICE, userId: userId })
  }

  public async unbindDevice(deviceId: string, userId: string) {
    await this.protocol.send(deviceId, COMMAND_TYPES.UNBIND_DEVICE, {
      type: COMMAND_TYPES.UNBIND_DEVICE,
      userId: userId,
    })
  }

  public async deleteFile(deviceId: string, key: string) {
    await this.protocol.send(deviceId, COMMAND_TYPES.DELETE_FILE, {
      type: COMMAND_TYPES.DELETE_FILE,
      key: key,
    })
  }

  public async prepareTransfer(deviceId: string, key: string, size: number) {
    await this.protocol.send(deviceId, COMMAND_TYPES.PREPARE_TRANSFER, {
      type: COMMAND_TYPES.PREPARE_TRANSFER,
      key: key,
      size: size,
    })
  }
}
