/**
 * Duooomi BLE SDK - Brownfield API Layer
 *
 * 对外暴露的蓝牙 SDK 接口，通过 expo-brownfield 的消息机制与原生 App 通信。
 * 原生 App 发送 action 消息 → SDK 执行蓝牙操作 → 通过事件/SharedState 返回结果。
 */
import Brownfield, {
  addMessageListener,
  sendMessage,
  setSharedStateValue,
} from 'expo-brownfield'
import { Platform, PermissionsAndroid, Linking, Alert } from 'react-native'
import { ScanMode } from 'react-native-ble-plx'

import { BleClient } from '../core/BleClient'
import { type BleDevice, type BleError, ConnectionState } from '../core/types'
import { BLE_UUIDS, 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 '../services/BleProtocolService'
import { DeviceInfoService } from '../services/DeviceInfoService'
import { FileTransferService } from '../services/FileTransferService'

// ====== SharedState Keys ======
const STATE_KEYS = {
  BT_STATE: 'btState', // 'idle' | 'scanning' | 'connecting' | 'connected' | 'disconnected'
  CONNECTED_DEVICE: 'connectedDevice', // { id, name } | null
  DEVICE_INFO: 'deviceInfo', // DeviceInfo | null
  VERSION: 'version', // string
  IS_ACTIVATED: 'isActivated', // boolean
  TRANSFER_PROGRESS: 'transferProgress', // number 0-100
  ERROR: 'error', // string | null
  DISCOVERED_DEVICES: 'discoveredDevices', // Array<{ id, name, rssi }>
} as const

class BleSDK {
  private bleClient = BleClient.getInstance()
  private deviceInfoService = DeviceInfoService.getInstance()
  private fileTransferService = FileTransferService.getInstance()
  private protocolService = BleProtocolService.getInstance()

  private connectedDeviceId: string | null = null
  private pendingOperations = new Map<
    string,
    {
      resolve: (value: any) => void
      reject: (reason?: any) => void
      timeoutId: ReturnType<typeof setTimeout>
    }
  >()

  // 批量设备发现缓冲
  private pendingDevices: Array<{ id: string; name: string | null; rssi: number | null }> = []
  private flushTimer: ReturnType<typeof setTimeout> | null = null

  initialize() {
    this.setupBleListeners()
    this.setupMessageHandler()
    this.updateState(STATE_KEYS.BT_STATE, 'idle')
    console.log('[BleSDK] Initialized')
  }

  // ====== 设置 BLE 事件监听 ======
  private setupBleListeners() {
    this.bleClient.addListener('connectionStateChange', ({ deviceId, state }: { deviceId: string; state: ConnectionState }) => {
      const stateMap: Record<ConnectionState, string> = {
        [ConnectionState.CONNECTING]: 'connecting',
        [ConnectionState.CONNECTED]: 'connected',
        [ConnectionState.DISCONNECTING]: 'disconnecting',
        [ConnectionState.DISCONNECTED]: 'disconnected',
      }
      this.updateState(STATE_KEYS.BT_STATE, stateMap[state] || 'idle')

      if (state === ConnectionState.DISCONNECTED) {
        this.connectedDeviceId = null
        this.updateState(STATE_KEYS.CONNECTED_DEVICE, null)
      }

      sendMessage({ event: 'connectionStateChange', deviceId, state })
    })

    this.bleClient.addListener('scanError', (error: BleError) => {
      this.updateState(STATE_KEYS.ERROR, error.message)
      sendMessage({ event: 'error', type: 'scanError', message: error.message })
    })

    // 设备信息响应
    this.deviceInfoService.addListener(EVENT_TYPES.DEVICE_INFO.name, (info: DeviceInfo) => {
      this.updateState(STATE_KEYS.DEVICE_INFO, info)
      this.resolvePending('deviceInfo', info)
      sendMessage({ event: 'deviceInfo', data: info })
    })

    this.deviceInfoService.addListener(EVENT_TYPES.VERSION_INFO.name, (info: VersionInfo) => {
      this.updateState(STATE_KEYS.VERSION, info.version)
      this.resolvePending('versionInfo', info)
      sendMessage({ event: 'versionInfo', data: info })
    })

    this.deviceInfoService.addListener(EVENT_TYPES.BIND_DEVICE.name, (resp: BindingResponse) => {
      this.updateState(STATE_KEYS.IS_ACTIVATED, resp.success === 1)
      if (resp.success === 1) {
        this.resolvePending('bindDevice', resp)
      } else {
        this.rejectPending('bindDevice', 'Device already bound to another user')
      }
      sendMessage({ event: 'bindDevice', data: resp })
    })

    this.deviceInfoService.addListener(EVENT_TYPES.UNBIND_DEVICE.name, (resp: UnBindResponse) => {
      if (resp.success === 1) {
        this.updateState(STATE_KEYS.IS_ACTIVATED, false)
        this.resolvePending('unbindDevice', resp)
      } else {
        this.rejectPending('unbindDevice', 'Unbind failed')
      }
      sendMessage({ event: 'unbindDevice', data: resp })
    })

    this.deviceInfoService.addListener(EVENT_TYPES.DELETE_FILE.name, (resp: DeleteFileResponse) => {
      if (resp.success === 0) {
        this.resolvePending('deleteFile', resp)
      } else {
        this.rejectPending('deleteFile', resp.success === 2 ? 'File not found' : 'Delete failed')
      }
      sendMessage({ event: 'deleteFile', data: resp })
    })

    this.deviceInfoService.addListener(EVENT_TYPES.PREPARE_TRANSFER.name, (resp: PrepareTransferResponse) => {
      const opId = `prepareTransfer_${resp.key}`
      if (resp.status === 'ready') {
        this.resolvePending(opId, resp)
      } else {
        this.rejectPending(opId, resp.status === 'no_space' ? 'No space on device' : 'File already exists')
      }
      sendMessage({ event: 'prepareTransfer', data: resp })
    })
  }

  // ====== 接收原生 App 的消息指令 ======
  private setupMessageHandler() {
    addMessageListener((event: Record<string, any>) => {
      const { action, ...params } = event
      console.log(`[BleSDK] Received action: ${action}`, params)

      switch (action) {
        case 'scan':
          this.startScan()
          break
        case 'stopScan':
          this.stopScan()
          break
        case 'connect':
          this.connect(params.deviceId)
          break
        case 'disconnect':
          this.disconnect()
          break
        case 'getDeviceInfo':
          this.getDeviceInfo()
          break
        case 'getVersion':
          this.getVersion()
          break
        case 'bind':
          this.bind(params.userId)
          break
        case 'unbind':
          this.unbind(params.userId)
          break
        case 'deleteFile':
          this.deleteFile(params.key)
          break
        case 'transferFile':
          this.transferFile(params.fileUri, params.commandType)
          break
        default:
          sendMessage({ event: 'error', type: 'unknownAction', message: `Unknown action: ${action}` })
      }
    })
  }

  // ====== 蓝牙操作实现 ======

  private async startScan() {
    try {
      const hasPerms = await this.requestPermissions()
      if (!hasPerms) {
        sendMessage({ event: 'error', type: 'permission', message: 'Bluetooth permission denied' })
        return
      }

      this.updateState(STATE_KEYS.BT_STATE, 'scanning')
      this.updateState(STATE_KEYS.DISCOVERED_DEVICES, [])
      this.pendingDevices = []

      await this.bleClient.startScan(
        [BLE_UUIDS.SERVICE],
        { scanMode: ScanMode.Balanced, allowDuplicates: false },
        (result) => {
          const device = result.device as BleDevice
          const hasService = device.serviceUUIDs?.some(
            (uuid) => uuid?.toLowerCase() === BLE_UUIDS.SERVICE.toLowerCase()
          )
          if (!hasService || !device?.id) return

          this.queueDevice({
            id: device.id,
            name: device.name || device.localName || null,
            rssi: result.rssi,
          })
        }
      )

      sendMessage({ event: 'scanStarted' })
    } catch (e: any) {
      this.updateState(STATE_KEYS.BT_STATE, 'idle')
      this.updateState(STATE_KEYS.ERROR, e.message)
      sendMessage({ event: 'error', type: 'scan', message: e.message })
    }
  }

  private stopScan() {
    this.bleClient.stopScan()
    this.flushDevices()
    this.updateState(STATE_KEYS.BT_STATE, this.connectedDeviceId ? 'connected' : 'idle')
    sendMessage({ event: 'scanStopped' })
  }

  private async connect(deviceId: string) {
    if (!deviceId) {
      sendMessage({ event: 'error', type: 'connect', message: 'deviceId is required' })
      return
    }

    this.stopScan()

    try {
      this.updateState(STATE_KEYS.BT_STATE, 'connecting')
      const device = await this.bleClient.connect(deviceId)

      this.connectedDeviceId = deviceId
      await this.protocolService.initialize(deviceId)

      this.updateState(STATE_KEYS.CONNECTED_DEVICE, {
        id: device.id,
        name: device.name || device.localName || null,
      })
      this.updateState(STATE_KEYS.BT_STATE, 'connected')

      sendMessage({ event: 'connected', deviceId: device.id, name: device.name })

      // 自动获取设备信息
      this.getDeviceInfo().catch(() => {})
    } catch (e: any) {
      this.updateState(STATE_KEYS.BT_STATE, 'idle')
      this.updateState(STATE_KEYS.ERROR, e.message)
      sendMessage({ event: 'error', type: 'connect', message: e.message })
    }
  }

  private async disconnect() {
    try {
      await this.bleClient.disconnect()
      this.protocolService.disconnect()
      this.connectedDeviceId = null

      this.updateState(STATE_KEYS.CONNECTED_DEVICE, null)
      this.updateState(STATE_KEYS.DEVICE_INFO, null)
      this.updateState(STATE_KEYS.VERSION, '')
      this.updateState(STATE_KEYS.IS_ACTIVATED, false)
      this.updateState(STATE_KEYS.BT_STATE, 'disconnected')

      sendMessage({ event: 'disconnected' })
    } catch (e: any) {
      sendMessage({ event: 'error', type: 'disconnect', message: e.message })
    }
  }

  private async getDeviceInfo() {
    if (!this.connectedDeviceId) return
    try {
      const promise = this.createWaitable<DeviceInfo>('deviceInfo')
      await this.deviceInfoService.getDeviceInfo(this.connectedDeviceId)
      return await promise
    } catch (e: any) {
      sendMessage({ event: 'error', type: 'deviceInfo', message: e.message })
    }
  }

  private async getVersion() {
    if (!this.connectedDeviceId) return
    try {
      const promise = this.createWaitable<VersionInfo>('versionInfo')
      await this.deviceInfoService.getDeviceVersion(this.connectedDeviceId)
      return await promise
    } catch (e: any) {
      sendMessage({ event: 'error', type: 'version', message: e.message })
    }
  }

  private async bind(userId: string) {
    if (!this.connectedDeviceId) return
    try {
      const promise = this.createWaitable<BindingResponse>('bindDevice')
      await this.deviceInfoService.bindDevice(this.connectedDeviceId, userId)
      return await promise
    } catch (e: any) {
      sendMessage({ event: 'error', type: 'bind', message: e.message })
    }
  }

  private async unbind(userId: string) {
    if (!this.connectedDeviceId) return
    try {
      const promise = this.createWaitable<UnBindResponse>('unbindDevice')
      await this.deviceInfoService.unbindDevice(this.connectedDeviceId, userId)
      return await promise
    } catch (e: any) {
      sendMessage({ event: 'error', type: 'unbind', message: e.message })
    }
  }

  private async deleteFile(key: string) {
    if (!this.connectedDeviceId) return
    try {
      const promise = this.createWaitable<DeleteFileResponse>('deleteFile')
      await this.deviceInfoService.deleteFile(this.connectedDeviceId, key)
      return await promise
    } catch (e: any) {
      sendMessage({ event: 'error', type: 'deleteFile', message: e.message })
    }
  }

  private async transferFile(fileUri: string, commandType?: number) {
    if (!this.connectedDeviceId) return
    try {
      this.updateState(STATE_KEYS.TRANSFER_PROGRESS, 0)
      const type = commandType ?? COMMAND_TYPES.TRANSFER_ANI_VIDEO

      await this.fileTransferService.transferFile(
        this.connectedDeviceId,
        fileUri,
        type,
        (progress) => {
          const pct = Math.round(progress * 100)
          this.updateState(STATE_KEYS.TRANSFER_PROGRESS, pct)
          sendMessage({ event: 'transferProgress', progress: pct })
        }
      )

      sendMessage({ event: 'transferComplete' })
    } catch (e: any) {
      this.updateState(STATE_KEYS.ERROR, e.message)
      sendMessage({ event: 'error', type: 'transfer', message: e.message })
    }
  }

  // ====== 工具方法 ======

  private updateState(key: string, value: any) {
    try {
      setSharedStateValue(key, value)
    } catch (e) {
      console.warn(`[BleSDK] Failed to set shared state ${key}:`, e)
    }
  }

  private queueDevice(device: { id: string; name: string | null; rssi: number | null }) {
    if (this.pendingDevices.some((d) => d.id === device.id)) return
    this.pendingDevices.push(device)

    if (this.flushTimer) clearTimeout(this.flushTimer)
    this.flushTimer = setTimeout(() => this.flushDevices(), 500)
  }

  private flushDevices() {
    if (this.pendingDevices.length === 0) return
    const devices = [...this.pendingDevices]
    this.pendingDevices = []
    this.updateState(STATE_KEYS.DISCOVERED_DEVICES, devices)
    sendMessage({ event: 'devicesFound', devices })
  }

  private createWaitable<T>(opId: string, timeout = 10000): Promise<T> {
    return new Promise<T>((resolve, reject) => {
      const timeoutId = setTimeout(() => {
        this.pendingOperations.delete(opId)
        reject(new Error(`Operation ${opId} timeout`))
      }, timeout)
      this.pendingOperations.set(opId, { resolve, reject, timeoutId })
    })
  }

  private resolvePending(opId: string, value: any) {
    const pending = this.pendingOperations.get(opId)
    if (pending) {
      clearTimeout(pending.timeoutId)
      this.pendingOperations.delete(opId)
      pending.resolve(value)
    }
  }

  private rejectPending(opId: string, error: string) {
    const pending = this.pendingOperations.get(opId)
    if (pending) {
      clearTimeout(pending.timeoutId)
      this.pendingOperations.delete(opId)
      pending.reject(new Error(error))
    }
  }

  private async requestPermissions(): Promise<boolean> {
    if (Platform.OS !== 'android') return true
    try {
      const sdk = Number(Platform.Version) || 0
      const perms: string[] = []
      if (sdk >= 31) {
        perms.push(PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN)
        perms.push(PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT)
      } else {
        perms.push(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION)
      }
      const results = await PermissionsAndroid.requestMultiple(perms as any)
      return Object.values(results).every((r) => r === PermissionsAndroid.RESULTS.GRANTED)
    } catch {
      return false
    }
  }
}

export const bleSDK = new BleSDK()
