const transport = require('../../transport/ble-core.js')
const {
  PROGRAM_CHUNK_SIZE,
  alignBootloaderBuffer,
  assertBootloaderAck,
  buildExitFrame,
  buildFlashCheckFrame,
  buildHandshakeFrame,
  buildPageEraseFrame,
  buildProgramFrame,
  buildUnlockFrame,
  calculateBootloaderCrc,
  formatBootloaderCrc,
  getBootloaderExpectedLength,
  parseBootloaderResponse,
  toHex
} = require('../../protocols/bootloader/frame.js')
const {
  softReset
} = require('../motor-control/protocol-service.js')
const {
  delay
} = require('../../utils/base-utils.js')
const {
  isCancelError,
  loadSelectedFile
} = require('../../repositories/file.js')
const {
  formatBytes
} = require('../../utils/binary-utils.js')

const HANDSHAKE_INTERVAL_MS = 200
const HANDSHAKE_ATTEMPTS = 10
const HANDSHAKE_TIMEOUT_MS = HANDSHAKE_INTERVAL_MS * HANDSHAKE_ATTEMPTS
const RESPONSE_TIMEOUT_MS = 3000
const PROGRAM_RESPONSE_TIMEOUT_MS = 6000
const FILE_SIZES = {
  16: 16 * 1024,
  32: 32 * 1024
}
const FLASH_LAYOUTS = {
  16: {
    capacity: FILE_SIZES[16],
    startAddress: 0x0400,
    endAddress: 0x4000
  },
  32: {
    capacity: FILE_SIZES[32],
    startAddress: 0x0800,
    endAddress: 0x8000
  }
}
const FLASH_SIZE_TEXT = Object.keys(FILE_SIZES)
  .map((sizeKb) => formatBytes(FILE_SIZES[sizeKb]))
  .join(' 或 ')
const CHIP_FLASH_SIZE_KB = {
  FU6572L: 32,
  FU6572N: 32,
  FU6572T: 32,
  FU6565N: 32,
  FU6565T: 32,
  FU6563N: 32,
  FU6562L: 32,
  FU6562LA: 32,
  FU6562Q: 32,
  FU6562S: 32,
  FU6562T: 32,
  FU6532N: 32,
  FU6532T: 32,
  FU6522L: 32,
  FU6522N: 32,
  FU6522T: 32,
  FU6812L2: 16,
  FU6812N2: 16,
  FU6812S2: 16,
  FU6812V: 16,
  FU6861Q2: 16,
  FU6861N2: 16,
  FU6861NF2: 16,
  FU6861L2: 16,
  FU6862L: 16,
  FU6862Q: 16,
  FU6872P: 16
}
const CHIP_FAMILY_FLASH_SIZE_KB = {
  65: 32,
  68: 16
}

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
let activeResponseWaiter = null
const subscribers = []

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

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

function abortActiveResponseWaiter(message) {
  if (!activeResponseWaiter) return false

  const waiter = activeResponseWaiter
  activeResponseWaiter = null
  waiter.abort(new Error(message || '蓝牙已断开'))
  return true
}

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) {
      abortActiveResponseWaiter('蓝牙已断开')
    }

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

  initialized = true
}

function normalizeModel(value) {
  const text = String(value || '').trim()
  return text && text !== '--' ? text : ''
}

function extractChipModels(chipModel) {
  const model = normalizeModel(chipModel).toUpperCase()
  return model.match(/FU\d{4}[A-Z0-9]*/g) || []
}

function inferFlashSizeKb(chipModel) {
  const models = extractChipModels(chipModel)

  for (const model of models) {
    if (CHIP_FLASH_SIZE_KB[model]) return CHIP_FLASH_SIZE_KB[model]

    const family = model.slice(2, 4)
    if (CHIP_FAMILY_FLASH_SIZE_KB[family]) return CHIP_FAMILY_FLASH_SIZE_KB[family]
  }

  const text = normalizeModel(chipModel).toUpperCase()
  if (/(^|[^0-9])32\s*K(B)?([^0-9]|$)/.test(text)) return 32
  if (/(^|[^0-9])16\s*K(B)?([^0-9]|$)/.test(text)) return 16

  return null
}

function inferFlashSizeKbFromBytes(byteLength) {
  return Number(Object.keys(FILE_SIZES).find((sizeKb) => FILE_SIZES[sizeKb] === byteLength)) || null
}

function inferUpgradeFlashSizeKb(chipModel, byteLength) {
  return inferFlashSizeKb(chipModel) || inferFlashSizeKbFromBytes(byteLength)
}

function getFlashLayout() {
  const sizeKb = inferUpgradeFlashSizeKb(state.chipModel, state.firmwareSize)
  return sizeKb ? FLASH_LAYOUTS[sizeKb] : null
}

function getFirmwareValidation(byteLength = state.firmwareSize, chipModel = state.chipModel) {
  const chipSizeKb = inferFlashSizeKb(chipModel)
  const firmwareSizeKb = inferFlashSizeKbFromBytes(byteLength)
  const sizeKb = chipSizeKb || firmwareSizeKb
  const layout = sizeKb ? FLASH_LAYOUTS[sizeKb] : null
  const normalizedChipModel = normalizeModel(chipModel)

  if (!byteLength) {
    return {
      isReady: false,
      text: chipSizeKb
        ? `需要 ${formatBytes(FLASH_LAYOUTS[chipSizeKb].capacity)} .bin`
        : `请选择 ${FLASH_SIZE_TEXT} .bin`
    }
  }

  if (!layout) {
    return {
      isReady: false,
      text: `文件 ${formatBytes(byteLength)}，应为 ${FLASH_SIZE_TEXT}`
    }
  }

  if (byteLength !== layout.capacity) {
    return {
      isReady: false,
      text: `文件 ${formatBytes(byteLength)}，应为 ${formatBytes(layout.capacity)}`
    }
  }

  return {
    isReady: true,
    text: chipSizeKb
      ? `匹配 ${formatBytes(layout.capacity)}`
      : `${normalizedChipModel ? `${normalizedChipModel} 未识别，` : '未读到芯片型号，'}按 ${formatBytes(layout.capacity)} 尝试`
  }
}

function setChipModel(chipModel) {
  const nextChipModel = normalizeModel(chipModel) || '--'
  const validation = 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 || '--'}`
}

function waitForResponse(kind, timeout, options = {}) {
  const expectedLength = getBootloaderExpectedLength(kind)
  const buffer = []

  return new Promise((resolve, reject) => {
    let settled = false
    let timer = null
    let unsubscribe = () => {}
    const waiter = {
      abort: (error) => {
        cleanup()
        reject(error)
      }
    }

    abortActiveResponseWaiter('新的 BootLoader 响应等待已开始')
    activeResponseWaiter = waiter

    unsubscribe = transport.subscribeRawResponse((bytes) => {
      buffer.push.apply(buffer, bytes)
      alignBootloaderBuffer(buffer)
      if (buffer.length < expectedLength) return

      const frame = buffer.slice(0, expectedLength)

      try {
        const response = parseBootloaderResponse(frame, kind)
        cleanup()
        resolve(response)
      } catch (error) {
        if (options.ignoreInvalid) {
          buffer.shift()
          return
        }

        cleanup()
        reject(error)
      }
    })
    timer = setTimeout(() => {
      cleanup()
      reject(new Error(`${kind} 响应超时`))
    }, timeout || RESPONSE_TIMEOUT_MS)

    function cleanup() {
      if (settled) return
      settled = true
      clearTimeout(timer)
      if (activeResponseWaiter === waiter) {
        activeResponseWaiter = null
      }
      unsubscribe()
    }
  })
}

async function sendBootloaderFrame(frame, label, kind, timeout) {
  const responsePromise = kind ? waitForResponse(kind, timeout) : null
  const sent = await transport.sendRawFrameExact(frame, label)

  if (!sent) {
    if (responsePromise) {
      responsePromise.catch(() => {})
      abortActiveResponseWaiter(`${label}发送失败`)
    }
    throw new Error(`${label}发送失败`)
  }

  return responsePromise ? responsePromise : true
}

async function sendSoftReset() {
  return softReset({
    kind: 'bootloader-soft-reset'
  })
}

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 = 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 transport.sendRawFrameExact(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) {
    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: '握手中'
  })

  const resetResponse = await sendSoftReset()
  setState({
    bootloaderDetailText: resetResponse ? '等待握手' : '软复位未响应，继续握手'
  })

  let lastError = null
  let finished = false
  const responsePromise = 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 transport.sendRawFrameExact(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 = 'message') {
  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 = getFirmwareValidation(firmwareBytes.length)

    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 || `请先选择 ${FLASH_SIZE_TEXT} .bin 文件`)
    return false
  }

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

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

  try {
    await handshakeUntilReady()

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

    setState({
      bootloaderDetailText: '开启页擦除',
      bootloaderStatusText: '升级中'
    })
    assertBootloaderAck(await sendBootloaderFrame(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 sendBootloaderFrame(
        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 sendBootloaderFrame(buildFlashCheckFrame(), '全Flash校验', 'flashCheck')
    await sendBootloaderFrame(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 sendBootloaderFrame(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 sendBootloaderFrame(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
}
