const transport = require('../../transport/ble-core.js')
const {
  PROGRAM_CHUNK_SIZE,
  assertBootloaderAck,
  buildExitFrame,
  buildFlashCheckFrame,
  buildHandshakeFrame,
  buildPageEraseFrame,
  buildProgramFrame,
  buildUnlockFrame,
  calculateBootloaderCrc,
  formatBootloaderCrc,
  toHex
} = require('../../protocols/bootloader/index.js')
const {
  delay
} = require('../../utils/base-utils.js')
const {
  isCancelError,
  loadSelectedFile
} = require('../../repositories/file.js')
const {
  formatBytes
} = require('../../utils/binary-utils.js')
const firmware = require('./firmware.js')
const bootloaderTransport = require('./transport.js')

const HANDSHAKE_INTERVAL_MS = 200
const HANDSHAKE_ATTEMPTS = 10
const HANDSHAKE_TIMEOUT_MS = HANDSHAKE_INTERVAL_MS * HANDSHAKE_ATTEMPTS
const PROGRAM_RESPONSE_TIMEOUT_MS = 6000

const state = {
  bootloaderChipId: '--',
  bootloaderDetailText: '',
  bootloaderProgress: 0,
  bootloaderStatusText: '',
  bootloaderVersion: '--',
  chipModel: '--',
  deviceProgramCrcText: '--',
  firmwareChecksumText: '--',
  firmwareName: '',
  firmwareSize: 0,
  firmwareSizeText: '--',
  firmwareValidText: '未选择',
  isBootloaderBusy: false,
  isFirmwareReady: false
}

let firmwareBytes = null
let initialized = false
let unsubscribeTransport = null
const subscribers = []

function getState() {
  return {
    ...state
  }
}

function setState(changedData) {
  Object.assign(state, changedData)
  subscribers.slice().forEach((subscriber) => {
    subscriber(getState())
  })
}

function subscribe(subscriber) {
  if (typeof subscriber !== 'function') return () => {}

  subscribers.push(subscriber)
  subscriber(getState())

  return () => {
    const index = subscribers.indexOf(subscriber)
    if (index >= 0) subscribers.splice(index, 1)
  }
}

function init() {
  transport.init()
  if (initialized) return

  unsubscribeTransport = transport.subscribe((transportState) => {
    if (!transportState.connectedDevice) {
      bootloaderTransport.abortActiveResponseWaiter('蓝牙已断开')
    }

    if (!transportState.connectedDevice && state.isBootloaderBusy) {
      transport.showCommandAlert('BootLoader', '蓝牙已断开，升级已停止')
      setState({
        bootloaderDetailText: '蓝牙已断开，升级已停止',
        bootloaderStatusText: '升级失败',
        isBootloaderBusy: false
      })
    }
  })

  initialized = true
}

function getFlashLayout() {
  return firmware.getFlashLayout(state.chipModel, state.firmwareSize)
}

function setChipModel(chipModel) {
  const nextChipModel = firmware.normalizeModel(chipModel) || '--'
  const validation = firmware.getFirmwareValidation(state.firmwareSize, nextChipModel)

  setState({
    chipModel: nextChipModel,
    bootloaderDetailText: validation.text,
    firmwareValidText: validation.text,
    isFirmwareReady: validation.isReady
  })
}

function getHandshakeDetail(response) {
  if (!response) return '--'

  return `${response.versionText || '--'} / ${response.chipIdText || '--'}`
}

async function sendHandshakeKeepAlive() {
  if (state.isBootloaderBusy) return false

  const frame = buildHandshakeFrame()
  let finished = false

  setState({
    bootloaderDetailText: `0/${HANDSHAKE_ATTEMPTS}`,
    bootloaderProgress: 0,
    bootloaderStatusText: '握手中',
    isBootloaderBusy: true
  })

  const responsePromise = bootloaderTransport.waitForResponse('handshake', HANDSHAKE_TIMEOUT_MS, {
    ignoreInvalid: true
  }).then((response) => {
    finished = true
    return response
  }).catch((error) => {
    finished = true
    throw error
  })
  responsePromise.catch(() => {})

  try {
    for (let attempt = 0; attempt < HANDSHAKE_ATTEMPTS && !finished; attempt += 1) {
      const sent = await bootloaderTransport.sendRawFrame(frame, 'Bootloader握手')
      if (!sent) throw new Error('握手帧发送失败')

      setState({
        bootloaderDetailText: `${attempt + 1}/${HANDSHAKE_ATTEMPTS}`,
        bootloaderProgress: Math.round((attempt + 1) / HANDSHAKE_ATTEMPTS * 100)
      })

      if (attempt < HANDSHAKE_ATTEMPTS - 1) {
        await delay(HANDSHAKE_INTERVAL_MS)
      }
    }

    const response = await responsePromise

    setState({
      bootloaderChipId: response.chipIdText,
      bootloaderDetailText: getHandshakeDetail(response),
      bootloaderProgress: 100,
      bootloaderStatusText: '握手成功',
      bootloaderVersion: response.versionText,
      isBootloaderBusy: false
    })
    return true
  } catch (error) {
    bootloaderTransport.abortActiveResponseWaiter('握手已停止')
    const message = error && error.message ? error.message : '握手失败'
    transport.showCommandAlert('BootLoader握手', message)
    setState({
      bootloaderDetailText: message,
      bootloaderStatusText: '握手失败',
      isBootloaderBusy: false
    })
    return false
  }
}

async function handshakeUntilReady() {
  const frame = buildHandshakeFrame()

  setState({
    bootloaderDetailText: '等待握手',
    bootloaderProgress: 0,
    bootloaderStatusText: '握手中'
  })

  let lastError = null
  let finished = false
  const responsePromise = bootloaderTransport.waitForResponse('handshake', HANDSHAKE_TIMEOUT_MS, {
    ignoreInvalid: true
  }).then((response) => {
    finished = true
    return response
  }).catch((error) => {
    finished = true
    throw error
  })

  for (let attempt = 0; attempt < HANDSHAKE_ATTEMPTS && !finished; attempt += 1) {
    try {
      await bootloaderTransport.sendRawFrame(frame, 'Bootloader握手')
    } catch (error) {
      lastError = error
    }

    if (!finished && attempt < HANDSHAKE_ATTEMPTS - 1) {
      await delay(HANDSHAKE_INTERVAL_MS)
    }
  }

  try {
    const response = await responsePromise

    setState({
      bootloaderChipId: response.chipIdText,
      bootloaderDetailText: getHandshakeDetail(response),
      bootloaderVersion: response.versionText,
      bootloaderStatusText: '握手成功'
    })

    return response
  } catch (error) {
    throw new Error(lastError ? `Bootloader握手失败：${lastError.message}` : `Bootloader握手失败：${error.message}`)
  }
}

async function chooseFirmwareFile(source = 'auto') {
  if (state.isBootloaderBusy) return false

  try {
    const file = await loadSelectedFile(source, {
      extensionMessage: '请选择 .bin 固件文件',
      extensions: ['bin'],
      fallbackName: 'firmware.bin'
    })
    firmwareBytes = file.bytes
    const firmwareCrcText = formatBootloaderCrc(calculateBootloaderCrc(firmwareBytes))
    const firmwareSizeText = formatBytes(firmwareBytes.length)
    const validation = firmware.getFirmwareValidation(firmwareBytes.length, state.chipModel)

    setState({
      bootloaderDetailText: validation.text,
      bootloaderProgress: 0,
      bootloaderStatusText: validation.isReady ? '固件已加载' : '固件不匹配',
      deviceProgramCrcText: '--',
      firmwareChecksumText: firmwareCrcText,
      firmwareName: file.name || 'firmware.bin',
      firmwareSize: firmwareBytes.length,
      firmwareSizeText,
      firmwareValidText: validation.text,
      isFirmwareReady: validation.isReady
    })

    return true
  } catch (error) {
    const message = error && (error.errMsg || error.message)
      ? (error.errMsg || error.message)
      : '读取固件失败'

    if (!isCancelError(error)) {
      transport.showCommandAlert('固件文件', message)
    }

    return false
  }
}

async function startUpgrade() {
  if (state.isBootloaderBusy) return false
  if (!firmwareBytes || !state.isFirmwareReady) {
    transport.showCommandAlert('固件不匹配', state.firmwareValidText || `请先选择 ${firmware.FLASH_SIZE_TEXT} .bin 文件`)
    return false
  }

  const layout = getFlashLayout()
  if (!layout) {
    transport.showCommandAlert('固件大小', `请选择 ${firmware.FLASH_SIZE_TEXT} .bin 文件`)
    return false
  }

  setState({
    bootloaderDetailText: '',
    bootloaderProgress: 0,
    bootloaderStatusText: '握手中',
    isBootloaderBusy: true
  })

  try {
    await handshakeUntilReady()

    setState({
      bootloaderDetailText: '编程解锁',
      bootloaderStatusText: '升级中'
    })
    assertBootloaderAck(await bootloaderTransport.sendFrame(buildUnlockFrame(), 'Bootloader解锁', 'unlock'), '编程解锁')

    setState({
      bootloaderDetailText: '开启页擦除',
      bootloaderStatusText: '升级中'
    })
    assertBootloaderAck(await bootloaderTransport.sendFrame(buildPageEraseFrame(true), '页擦除使能', 'pageErase'), '页擦除使能')

    const totalBytes = layout.endAddress - layout.startAddress
    let programmedBytes = 0

    for (let address = layout.startAddress; address < layout.endAddress; address += PROGRAM_CHUNK_SIZE) {
      const chunk = firmwareBytes.slice(address, address + PROGRAM_CHUNK_SIZE)
      const response = await bootloaderTransport.sendFrame(
        buildProgramFrame(address, chunk),
        `编程 0x${toHex(address, 4)}`,
        'program',
        PROGRAM_RESPONSE_TIMEOUT_MS
      )

      assertBootloaderAck(response, `编程 0x${toHex(address, 4)}`)
      if (response.address !== address) {
        throw new Error(`编程地址反馈不匹配：0x${toHex(response.address, 4)}`)
      }

      programmedBytes = Math.min(totalBytes, programmedBytes + PROGRAM_CHUNK_SIZE)
      const progress = Math.min(99, Math.round(programmedBytes / totalBytes * 100))
      setState({
        bootloaderDetailText: `0x${toHex(address, 4)}`,
        bootloaderProgress: progress,
        bootloaderStatusText: `升级中 ${progress}%`
      })
    }

    const checkResponse = await bootloaderTransport.sendFrame(buildFlashCheckFrame(), '全Flash校验', 'flashCheck')
    await bootloaderTransport.sendFrame(buildExitFrame(), '退出Bootloader')

    setState({
      bootloaderDetailText: '校验通过',
      bootloaderProgress: 100,
      bootloaderStatusText: '升级完成',
      deviceProgramCrcText: checkResponse.flashCrcText,
      isBootloaderBusy: false
    })
    return true
  } catch (error) {
    const message = error && error.message ? error.message : '升级失败'
    transport.showCommandAlert('Bootloader升级', message)
    setState({
      bootloaderDetailText: message,
      bootloaderStatusText: '升级失败',
      isBootloaderBusy: false
    })
    return false
  }
}

async function readProgramChecksum() {
  if (state.isBootloaderBusy) return false

  setState({
    bootloaderDetailText: '',
    bootloaderStatusText: '读取中'
  })

  try {
    const response = await bootloaderTransport.sendFrame(buildFlashCheckFrame(), '读取程序校验码', 'flashCheck')

    setState({
      bootloaderDetailText: '程序校验码已读取',
      bootloaderStatusText: '读取完成',
      deviceProgramCrcText: response.flashCrcText
    })
    return true
  } catch (error) {
    const message = error && error.message ? error.message : '读取程序校验码失败'
    transport.showCommandAlert('程序校验码', message)
    setState({
      bootloaderDetailText: message,
      bootloaderStatusText: '读取失败'
    })
    return false
  }
}

async function exitBootloader() {
  if (state.isBootloaderBusy) return false

  try {
    const sent = await bootloaderTransport.sendFrame(buildExitFrame(), '退出BootLoader')
    if (!sent) throw new Error('退出命令发送失败')

    setState({
      bootloaderDetailText: '',
      bootloaderStatusText: '已退出 BootLoader'
    })
    return true
  } catch (error) {
    const message = error && error.message ? error.message : '退出 BootLoader 失败'
    transport.showCommandAlert('退出 BootLoader', message)
    setState({
      bootloaderDetailText: message,
      bootloaderStatusText: '退出失败'
    })
    return false
  }
}

module.exports = {
  chooseFirmwareFile,
  getState,
  init,
  readProgramChecksum,
  sendHandshakeKeepAlive,
  setChipModel,
  exitBootloader,
  startUpgrade,
  subscribe
}
