export interface HeartRateControllerCallbacks {
  onHeartRate: (bpm: number) => void
  onStatus: (message: string) => void
  onError: (message: string) => void
  onConnectionChange: (connected: boolean, deviceName: string | null) => void
  onDeviceListChange: (devices: HeartRateDiscoveredDevice[]) => void
}

export interface HeartRateDiscoveredDevice {
  deviceId: string
  name: string
  rssi: number | null
  lastSeenAt: number
  isPreferred: boolean
}

type BluetoothDeviceLike = {
  deviceId?: string
  name?: string
  localName?: string
  RSSI?: number
  advertisServiceUUIDs?: string[]
}

const HEART_RATE_SERVICE_UUID = '180D'
const HEART_RATE_MEASUREMENT_UUID = '2A37'
const DISCOVERY_TIMEOUT_MS = 12000
const DEVICE_STALE_MS = 15000
const PREFERRED_HEART_RATE_DEVICE_STORAGE_KEY = 'cmr.preferredHeartRateDevice'

function normalizeUuid(uuid: string | undefined | null): string {
  return String(uuid || '').replace(/[^0-9a-f]/gi, '').toUpperCase()
}

function matchesShortUuid(uuid: string | undefined | null, shortUuid: string): boolean {
  const normalized = normalizeUuid(uuid)
  const normalizedShort = normalizeUuid(shortUuid)
  if (!normalized || !normalizedShort) {
    return false
  }

  return normalized === normalizedShort
    || normalized.indexOf(`0000${normalizedShort}00001000800000805F9B34FB`) === 0
    || normalized.endsWith(normalizedShort)
}

function getDeviceDisplayName(device: BluetoothDeviceLike | null | undefined): string {
  if (!device) {
    return '心率带'
  }

  return device.name || device.localName || '未命名心率带'
}

function isHeartRateDevice(device: BluetoothDeviceLike): boolean {
  const serviceIds = Array.isArray(device.advertisServiceUUIDs) ? device.advertisServiceUUIDs : []
  if (serviceIds.some((uuid) => matchesShortUuid(uuid, HEART_RATE_SERVICE_UUID))) {
    return true
  }

  const name = `${device.name || ''} ${device.localName || ''}`.toUpperCase()
  return name.indexOf('HR') !== -1
    || name.indexOf('HEART') !== -1
    || name.indexOf('POLAR') !== -1
    || name.indexOf('GARMIN') !== -1
    || name.indexOf('COOSPO') !== -1
}

function parseHeartRateMeasurement(buffer: ArrayBuffer): number | null {
  if (!buffer || buffer.byteLength < 2) {
    return null
  }

  const view = new DataView(buffer)
  const flags = view.getUint8(0)
  const isUint16 = (flags & 0x01) === 0x01

  if (isUint16) {
    if (buffer.byteLength < 3) {
      return null
    }
    return view.getUint16(1, true)
  }

  return view.getUint8(1)
}

export class HeartRateController {
  callbacks: HeartRateControllerCallbacks
  scanning: boolean
  connecting: boolean
  connected: boolean
  currentDeviceId: string | null
  currentDeviceName: string | null
  discoveredDevices: HeartRateDiscoveredDevice[]
  lastDeviceId: string | null
  lastDeviceName: string | null
  manualDisconnect: boolean
  reconnecting: boolean
  disconnecting: boolean
  disconnectingDeviceId: string | null
  autoConnectDeviceId: string | null
  measurementServiceId: string | null
  measurementCharacteristicId: string | null
  discoveryTimer: number
  reconnectTimer: number
  deviceFoundHandler: ((result: any) => void) | null
  characteristicHandler: ((result: any) => void) | null
  connectionStateHandler: ((result: any) => void) | null

  constructor(callbacks: HeartRateControllerCallbacks) {
    this.callbacks = callbacks
    this.scanning = false
    this.connecting = false
    this.connected = false
    this.currentDeviceId = null
    this.currentDeviceName = null
    this.discoveredDevices = []
    this.lastDeviceId = null
    this.lastDeviceName = null
    this.manualDisconnect = false
    this.reconnecting = false
    this.disconnecting = false
    this.disconnectingDeviceId = null
    this.autoConnectDeviceId = null
    this.measurementServiceId = null
    this.measurementCharacteristicId = null
    this.discoveryTimer = 0
    this.reconnectTimer = 0
    this.deviceFoundHandler = null
    this.characteristicHandler = null
    this.connectionStateHandler = null
    this.restorePreferredDevice()
  }

  startScanAndConnect(): void {
    if (this.connected) {
      this.callbacks.onStatus(`心率带已连接: ${this.currentDeviceName || '设备'}`)
      return
    }

    if (this.disconnecting) {
      this.callbacks.onStatus('心率带断开中，请稍后再试')
      return
    }

    if (this.scanning || this.connecting) {
      this.callbacks.onStatus('心率带连接进行中')
      return
    }

    this.manualDisconnect = false
    this.reconnecting = false
    this.clearReconnectTimer()

    this.withFreshBluetoothAdapter(() => {
      if (this.lastDeviceId) {
        this.callbacks.onStatus(`正在扫描并优先连接 ${this.lastDeviceName || '心率带'}`)
        this.beginDiscovery()
        return
      }

      this.beginDiscovery()
    })
  }

  disconnect(): void {
    this.clearDiscoveryTimer()
    this.clearReconnectTimer()
    this.stopDiscovery()

    const deviceId = this.currentDeviceId
    this.connecting = false
    this.reconnecting = false
    this.manualDisconnect = true
    this.disconnecting = true

    if (!deviceId) {
      this.disconnecting = false
      this.clearConnectionState()
      this.callbacks.onStatus('心率带未连接')
      return
    }

    this.disconnectingDeviceId = deviceId
    const wxAny = wx as any
    wxAny.closeBLEConnection({
      deviceId,
      complete: () => {
        this.disconnecting = false
        this.clearConnectionState()
        this.callbacks.onStatus('心率带已断开')
      },
    })
  }

  destroy(): void {
    this.clearDiscoveryTimer()
    this.clearReconnectTimer()
    this.stopDiscovery()
    this.detachListeners()

    const deviceId = this.currentDeviceId
    if (deviceId) {
      const wxAny = wx as any
      wxAny.closeBLEConnection({
        deviceId,
        complete: () => {},
      })
    }

    const wxAny = wx as any
    if (typeof wxAny.closeBluetoothAdapter === 'function') {
      wxAny.closeBluetoothAdapter({
        complete: () => {},
      })
    }

    this.clearConnectionState()
  }

  beginDiscovery(): void {
    if (this.scanning || this.connecting || this.connected) {
      return
    }

    this.bindListeners()
    this.autoConnectDeviceId = this.lastDeviceId
    const wxAny = wx as any
    wxAny.startBluetoothDevicesDiscovery({
      allowDuplicatesKey: false,
      services: [HEART_RATE_SERVICE_UUID],
      success: () => {
        this.scanning = true
        this.pruneDiscoveredDevices()
        this.callbacks.onStatus(this.autoConnectDeviceId ? '正在扫描心率带并等待自动连接' : '正在扫描心率带，请选择设备')
        this.clearDiscoveryTimer()
        this.discoveryTimer = setTimeout(() => {
          this.discoveryTimer = 0
          if (!this.scanning || this.connected || this.connecting) {
            return
          }

          this.stopDiscovery()
          this.callbacks.onError(this.discoveredDevices.length ? '已发现心率带，请从列表选择连接' : '未发现可连接的心率带')
        }, DISCOVERY_TIMEOUT_MS) as unknown as number
      },
      fail: (error: any) => {
        const message = error && error.errMsg ? error.errMsg : 'startBluetoothDevicesDiscovery 失败'
        this.callbacks.onError(`扫描心率带失败: ${message}`)
      },
    })
  }

  stopDiscovery(): void {
    this.clearDiscoveryTimer()

    if (!this.scanning) {
      return
    }

    this.scanning = false
    this.autoConnectDeviceId = null
    const wxAny = wx as any
    wxAny.stopBluetoothDevicesDiscovery({
      complete: () => {},
    })
  }

  bindListeners(): void {
    const wxAny = wx as any

    if (!this.deviceFoundHandler) {
      this.deviceFoundHandler = (result: any) => {
        const devices = Array.isArray(result && result.devices)
          ? result.devices
          : result && result.deviceId
            ? [result]
            : []

        this.mergeDiscoveredDevices(devices)
        if (!this.scanning || this.connecting || this.connected) {
          return
        }

        const targetDevice = this.selectTargetDevice(devices)
        if (!targetDevice || !targetDevice.deviceId) {
          return
        }

        this.stopDiscovery()
        this.connectToDevice(targetDevice.deviceId, getDeviceDisplayName(targetDevice))
      }

      if (typeof wxAny.onBluetoothDeviceFound === 'function') {
        wxAny.onBluetoothDeviceFound(this.deviceFoundHandler)
      }
    }

    if (!this.characteristicHandler) {
      this.characteristicHandler = (result: any) => {
        if (!result || !result.value) {
          return
        }

        if (this.currentDeviceId && result.deviceId && result.deviceId !== this.currentDeviceId) {
          return
        }

        if (!matchesShortUuid(result.characteristicId, HEART_RATE_MEASUREMENT_UUID)) {
          return
        }

        const bpm = parseHeartRateMeasurement(result.value)
        if (bpm === null || !Number.isFinite(bpm) || bpm <= 0) {
          return
        }

        this.callbacks.onHeartRate(Math.round(bpm))
      }

      if (typeof wxAny.onBLECharacteristicValueChange === 'function') {
        wxAny.onBLECharacteristicValueChange(this.characteristicHandler)
      }
    }

    if (!this.connectionStateHandler) {
      this.connectionStateHandler = (result: any) => {
        if (!result || !result.deviceId) {
          return
        }

        if (result.connected) {
          return
        }

        if (this.disconnectingDeviceId && result.deviceId === this.disconnectingDeviceId) {
          this.disconnectingDeviceId = null
          return
        }

        if (!this.currentDeviceId || result.deviceId !== this.currentDeviceId) {
          return
        }

        const disconnectedDeviceId = this.currentDeviceId
        const disconnectedDeviceName = this.currentDeviceName
        this.clearConnectionState()
        this.callbacks.onStatus('心率带连接已断开')
        this.scheduleAutoReconnect(disconnectedDeviceId, disconnectedDeviceName)
      }

      if (typeof wxAny.onBLEConnectionStateChange === 'function') {
        wxAny.onBLEConnectionStateChange(this.connectionStateHandler)
      }
    }
  }

  detachListeners(): void {
    const wxAny = wx as any

    if (this.deviceFoundHandler && typeof wxAny.offBluetoothDeviceFound === 'function') {
      wxAny.offBluetoothDeviceFound(this.deviceFoundHandler)
    }
    if (this.characteristicHandler && typeof wxAny.offBLECharacteristicValueChange === 'function') {
      wxAny.offBLECharacteristicValueChange(this.characteristicHandler)
    }
    if (this.connectionStateHandler && typeof wxAny.offBLEConnectionStateChange === 'function') {
      wxAny.offBLEConnectionStateChange(this.connectionStateHandler)
    }

    this.deviceFoundHandler = null
    this.characteristicHandler = null
    this.connectionStateHandler = null
  }

  connectToDevice(deviceId: string, deviceName: string, fallbackToDiscovery: boolean = false): void {
    this.connecting = true
    this.reconnecting = false
    this.disconnecting = false
    this.manualDisconnect = false
    this.disconnectingDeviceId = null
    this.autoConnectDeviceId = deviceId
    this.currentDeviceId = deviceId
    this.currentDeviceName = deviceName
    this.callbacks.onStatus(`正在连接 ${deviceName}`)

    const wxAny = wx as any
    wxAny.createBLEConnection({
      deviceId,
      timeout: 10000,
      success: () => {
        this.discoverMeasurementCharacteristic(deviceId, deviceName)
      },
      fail: (error: any) => {
        const message = error && error.errMsg ? error.errMsg : 'createBLEConnection 失败'
        this.clearConnectionState()
        if (fallbackToDiscovery && !this.manualDisconnect) {
          this.callbacks.onStatus(`直连失败，转入扫描: ${deviceName}`)
          this.beginDiscovery()
          return
        }
        this.callbacks.onError(`连接心率带失败: ${message}`)
      },
    })
  }

  discoverMeasurementCharacteristic(deviceId: string, deviceName: string): void {
    const wxAny = wx as any
    wxAny.getBLEDeviceServices({
      deviceId,
      success: (serviceResult: any) => {
        const services = Array.isArray(serviceResult && serviceResult.services) ? serviceResult.services : []
        const service = services.find((item: any) => matchesShortUuid(item && item.uuid, HEART_RATE_SERVICE_UUID))
        if (!service || !service.uuid) {
          this.failConnection(deviceId, '未找到标准心率服务')
          return
        }

        wxAny.getBLEDeviceCharacteristics({
          deviceId,
          serviceId: service.uuid,
          success: (characteristicResult: any) => {
            const characteristics = Array.isArray(characteristicResult && characteristicResult.characteristics)
              ? characteristicResult.characteristics
              : []
            const characteristic = characteristics.find((item: any) => {
              const properties = item && item.properties ? item.properties : {}
              return matchesShortUuid(item && item.uuid, HEART_RATE_MEASUREMENT_UUID)
                && (properties.notify || properties.indicate)
            })

            if (!characteristic || !characteristic.uuid) {
              this.failConnection(deviceId, '未找到心率通知特征')
              return
            }

            this.measurementServiceId = service.uuid
            this.measurementCharacteristicId = characteristic.uuid
            wxAny.notifyBLECharacteristicValueChange({
              state: true,
              deviceId,
              serviceId: service.uuid,
              characteristicId: characteristic.uuid,
              success: () => {
                this.connecting = false
                this.connected = true
                this.lastDeviceId = deviceId
                this.lastDeviceName = deviceName
                this.persistPreferredDevice()
                this.manualDisconnect = false
                this.reconnecting = false
                this.disconnectingDeviceId = null
                this.autoConnectDeviceId = deviceId
                this.refreshPreferredFlags()
                this.clearReconnectTimer()
                this.callbacks.onConnectionChange(true, deviceName)
                this.callbacks.onStatus(`心率带已连接: ${deviceName}`)
              },
              fail: (error: any) => {
                const message = error && error.errMsg ? error.errMsg : 'notifyBLECharacteristicValueChange 失败'
                this.failConnection(deviceId, `心率订阅失败: ${message}`)
              },
            })
          },
          fail: (error: any) => {
            const message = error && error.errMsg ? error.errMsg : 'getBLEDeviceCharacteristics 失败'
            this.failConnection(deviceId, `读取心率特征失败: ${message}`)
          },
        })
      },
      fail: (error: any) => {
        const message = error && error.errMsg ? error.errMsg : 'getBLEDeviceServices 失败'
        this.failConnection(deviceId, `读取心率服务失败: ${message}`)
      },
    })
  }

  failConnection(deviceId: string, message: string): void {
    const wxAny = wx as any
    wxAny.closeBLEConnection({
      deviceId,
      complete: () => {
        this.clearConnectionState()
        this.callbacks.onError(message)
        this.scheduleAutoReconnect(this.lastDeviceId, this.lastDeviceName)
      },
    })
  }

  clearConnectionState(): void {
    const wasConnected = this.connected
    this.scanning = false
    this.connecting = false
    this.connected = false
    this.disconnecting = false
    this.disconnectingDeviceId = null
    this.autoConnectDeviceId = null
    this.currentDeviceId = null
    this.measurementServiceId = null
    this.measurementCharacteristicId = null
    const previousDeviceName = this.currentDeviceName
    this.currentDeviceName = null
    if (wasConnected || previousDeviceName) {
      this.callbacks.onConnectionChange(false, null)
    }
  }

  clearDiscoveryTimer(): void {
    if (this.discoveryTimer) {
      clearTimeout(this.discoveryTimer)
      this.discoveryTimer = 0
    }
  }

  clearReconnectTimer(): void {
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer)
      this.reconnectTimer = 0
    }
  }

  selectTargetDevice(devices: BluetoothDeviceLike[]): BluetoothDeviceLike | null {
    if (!Array.isArray(devices) || !devices.length) {
      return null
    }

    if (this.autoConnectDeviceId) {
      const rememberedDevice = devices.find((device) => device && device.deviceId === this.autoConnectDeviceId)
      if (rememberedDevice && isHeartRateDevice(rememberedDevice)) {
        return rememberedDevice
      }

      return null
    }

    return null
  }

  scheduleAutoReconnect(deviceId: string | null, deviceName: string | null): void {
    if (this.manualDisconnect || !deviceId || this.connected || this.connecting || this.scanning) {
      return
    }

    this.lastDeviceId = deviceId
    this.lastDeviceName = deviceName || this.lastDeviceName
    this.clearReconnectTimer()
    this.reconnecting = true
    this.callbacks.onStatus(`心率带已断开，等待自动重连: ${this.lastDeviceName || '设备'}`)
    this.reconnectTimer = setTimeout(() => {
      this.reconnectTimer = 0
      if (this.manualDisconnect || this.connected || this.connecting || this.scanning) {
        return
      }

      const rememberedDeviceId = this.lastDeviceId
      const rememberedDeviceName = this.lastDeviceName || '心率带'
      this.reconnecting = false
        this.withFreshBluetoothAdapter(() => {
          if (rememberedDeviceId) {
            this.callbacks.onStatus(`正在自动重连 ${rememberedDeviceName}`)
            this.resetConnectionAndConnect(rememberedDeviceId, rememberedDeviceName, true)
            return
          }

          this.beginDiscovery()
        })
      }, 600) as unknown as number
  }

  resetConnectionAndConnect(deviceId: string, deviceName: string, fallbackToDiscovery: boolean): void {
    const wxAny = wx as any
    this.disconnectingDeviceId = deviceId
    wxAny.closeBLEConnection({
      deviceId,
      complete: () => {
        if (this.disconnectingDeviceId === deviceId) {
          this.disconnectingDeviceId = null
        }

        setTimeout(() => {
          if (this.connected || this.connecting || this.scanning || this.disconnecting) {
            return
          }

          this.connectToDevice(deviceId, deviceName, fallbackToDiscovery)
        }, 320)
      },
    })
  }

  connectToDiscoveredDevice(deviceId: string): void {
    if (!deviceId) {
      return
    }

    if (this.disconnecting) {
      this.callbacks.onStatus('心率带断开中，请稍后再试')
      return
    }

    const targetDevice = this.discoveredDevices.find((device) => device.deviceId === deviceId)
    const deviceName = targetDevice ? targetDevice.name : (this.lastDeviceId === deviceId ? (this.lastDeviceName || '心率带') : '心率带')
    this.lastDeviceId = deviceId
    this.lastDeviceName = deviceName
    this.refreshPreferredFlags()
    this.stopDiscovery()

    this.withFreshBluetoothAdapter(() => {
      this.callbacks.onStatus(`正在连接 ${deviceName}`)
      this.resetConnectionAndConnect(deviceId, deviceName, true)
    })
  }

  clearPreferredDevice(): void {
    this.lastDeviceId = null
    this.lastDeviceName = null
    this.autoConnectDeviceId = null
    this.removePreferredDevice()
    this.refreshPreferredFlags()
    this.callbacks.onStatus('已清除首选心率带')
  }

  mergeDiscoveredDevices(devices: BluetoothDeviceLike[]): void {
    if (!Array.isArray(devices) || !devices.length) {
      return
    }

    const now = Date.now()
    let changed = false
    const nextDevices = [...this.discoveredDevices]
    for (const rawDevice of devices) {
      if (!rawDevice || !rawDevice.deviceId || !isHeartRateDevice(rawDevice)) {
        continue
      }

      const name = getDeviceDisplayName(rawDevice)
      const rssi = typeof rawDevice.RSSI === 'number' && Number.isFinite(rawDevice.RSSI) ? rawDevice.RSSI : null
      const existingIndex = nextDevices.findIndex((item) => item.deviceId === rawDevice.deviceId)
      const nextDevice: HeartRateDiscoveredDevice = {
        deviceId: rawDevice.deviceId,
        name,
        rssi,
        lastSeenAt: now,
        isPreferred: rawDevice.deviceId === this.lastDeviceId,
      }

      if (existingIndex >= 0) {
        nextDevices[existingIndex] = nextDevice
      } else {
        nextDevices.push(nextDevice)
      }
      changed = true
    }

    if (!changed) {
      return
    }

    this.discoveredDevices = this.sortDiscoveredDevices(nextDevices.filter((device) => now - device.lastSeenAt <= DEVICE_STALE_MS))
    this.callbacks.onDeviceListChange([...this.discoveredDevices])
  }

  pruneDiscoveredDevices(): void {
    const now = Date.now()
    const nextDevices = this.sortDiscoveredDevices(
      this.discoveredDevices.filter((device) => now - device.lastSeenAt <= DEVICE_STALE_MS),
    )
    const changed = nextDevices.length !== this.discoveredDevices.length
      || nextDevices.some((device, index) => {
        const previous = this.discoveredDevices[index]
        return !previous || previous.deviceId !== device.deviceId || previous.isPreferred !== device.isPreferred || previous.rssi !== device.rssi
      })

    this.discoveredDevices = nextDevices
    if (changed) {
      this.callbacks.onDeviceListChange([...this.discoveredDevices])
    }
  }

  refreshPreferredFlags(): void {
    if (!this.discoveredDevices.length) {
      this.callbacks.onDeviceListChange([])
      return
    }

    this.discoveredDevices = this.sortDiscoveredDevices(
      this.discoveredDevices.map((device) => ({
        ...device,
        isPreferred: !!this.lastDeviceId && device.deviceId === this.lastDeviceId,
      })),
    )
    this.callbacks.onDeviceListChange([...this.discoveredDevices])
  }

  sortDiscoveredDevices(devices: HeartRateDiscoveredDevice[]): HeartRateDiscoveredDevice[] {
    return [...devices].sort((a, b) => {
      if (a.isPreferred !== b.isPreferred) {
        return a.isPreferred ? -1 : 1
      }

      const aRssi = a.rssi === null ? -999 : a.rssi
      const bRssi = b.rssi === null ? -999 : b.rssi
      if (aRssi !== bRssi) {
        return bRssi - aRssi
      }

      return b.lastSeenAt - a.lastSeenAt
    })
  }

  withFreshBluetoothAdapter(onReady: () => void): void {
    const wxAny = wx as any
    const openAdapter = () => {
      wxAny.openBluetoothAdapter({
        success: () => {
          onReady()
        },
        fail: (error: any) => {
          const message = error && error.errMsg ? error.errMsg : 'openBluetoothAdapter 失败'
          this.callbacks.onError(`蓝牙不可用: ${message}`)
        },
      })
    }

    if (typeof wxAny.closeBluetoothAdapter !== 'function') {
      openAdapter()
      return
    }

    wxAny.closeBluetoothAdapter({
      complete: () => {
        setTimeout(() => {
          openAdapter()
        }, 180)
      },
    })
  }

  restorePreferredDevice(): void {
    try {
      const stored = wx.getStorageSync(PREFERRED_HEART_RATE_DEVICE_STORAGE_KEY)
      if (!stored || typeof stored !== 'object') {
        return
      }

      const normalized = stored as { deviceId?: unknown; name?: unknown }
      if (typeof normalized.deviceId !== 'string' || !normalized.deviceId) {
        return
      }

      this.lastDeviceId = normalized.deviceId
      this.lastDeviceName = typeof normalized.name === 'string' && normalized.name ? normalized.name : '心率带'
    } catch {}
  }

  persistPreferredDevice(): void {
    if (!this.lastDeviceId) {
      return
    }

    try {
      wx.setStorageSync(PREFERRED_HEART_RATE_DEVICE_STORAGE_KEY, {
        deviceId: this.lastDeviceId,
        name: this.lastDeviceName || '心率带',
      })
    } catch {}
  }

  removePreferredDevice(): void {
    try {
      wx.removeStorageSync(PREFERRED_HEART_RATE_DEVICE_STORAGE_KEY)
    } catch {}
  }
}
