const storageAccessProtocol = require('../../protocols/storage-access/index.js')
const settingsService = require('../../store/settings-store.js')
const transport = require('../../transport/ble-core.js')
const {
  createGroupsFromCodeInfo,
  parseCodeInfo
} = require('../../domain/storage-access/code-info-parser.js')
const {
  cloneImportedGroup,
  normalizeGroup
} = require('../../domain/parameter-groups/model.js')
const {
  bytesToHex
} = require('../../utils/binary-utils.js')
const {
  normalizeHexDwordText,
  normalizeHexText,
  normalizeHexWordText,
  parseHexBytes,
  parseHexDword,
  parseHexNumber,
  validateHexDwordText,
  validateHexText,
  validateHexWordText
} = require('../../utils/validation.js')

const MEMORY_COMMANDS = [
  { key: 'read', label: '读取', description: '按字节读取内存' },
  { key: 'write', label: '写入', description: '按字节写入内存' }
]

const MEMORY_AREAS = [
  { key: storageAccessProtocol.AREA.ADDR32, label: 'addr32', name: 'ADDR32', addressWidth: 32 },
  { key: storageAccessProtocol.AREA.CODEINFO, label: 'codeinfo', name: 'CODEINFO', readOnly: true },
  { key: storageAccessProtocol.AREA.DATA, label: 'data', name: 'DATA' },
  { key: storageAccessProtocol.AREA.IDATA, label: 'idata', name: 'IDATA' },
  { key: storageAccessProtocol.AREA.XDATA, label: 'xdata', name: 'XDATA' },
  { key: storageAccessProtocol.AREA.CODE, label: 'code', name: 'CODE', readOnly: true }
]
const MEMORY_READ_INDEX = 0
const MEMORY_WRITE_INDEX = 1

const CONTROL_COMMANDS = [
  { key: 'reset', label: '复位', op: storageAccessProtocol.CONTROL_OP.RESET }
]

let syncedDeviceCaps = {
  addressWidth: 0,
  maxPacketLength: 0,
  memoryEndian: ''
}

function resolveMaxPacketLength(value) {
  const settings = settingsService.getState()
  const numberValue = Number(value === undefined ? settings.parameterMaxPacketLength : value)
  const deviceMaxPacketLength = Number(syncedDeviceCaps.maxPacketLength || 0)
  const configuredMaxPacketLength = Number.isFinite(numberValue) && Math.round(numberValue) === 0
    ? 0
    : (Number.isFinite(numberValue) && numberValue > 0 ? Math.round(numberValue) : 64)

  if (!Number.isFinite(deviceMaxPacketLength) || deviceMaxPacketLength <= 0) return configuredMaxPacketLength
  if (configuredMaxPacketLength === 0) return Math.round(deviceMaxPacketLength)

  return Math.min(configuredMaxPacketLength, Math.round(deviceMaxPacketLength))
}

function resolveConfiguredMaxPacketLength(value) {
  const settings = settingsService.getState()
  const numberValue = Number(value === undefined ? settings.parameterMaxPacketLength : value)
  if (Number.isFinite(numberValue) && Math.round(numberValue) === 0) return 0
  if (Number.isFinite(numberValue) && numberValue > 0) return Math.round(numberValue)

  return 64
}

function normalizeTransferOptions(options = {}) {
  const maxPacketLength = options.maxPacketLength === undefined ? options.maxFrameBytes : options.maxPacketLength

  return {
    expectedByteLength: options.expectedByteLength,
    maxFrameBytes: options.useDeviceCaps === false
      ? resolveConfiguredMaxPacketLength(maxPacketLength)
      : resolveMaxPacketLength(maxPacketLength),
    onChunk: options.onChunk,
    showModal: options.showModal !== false
  }
}

function updateSyncedDeviceCaps(caps = {}) {
  const addressWidth = Number(caps.addressWidth)
  const maxPacketLength = Number(caps.maxPacketLength)
  const memoryEndian = String(caps.memoryEndian || '').trim().toLowerCase()

  syncedDeviceCaps = {
    addressWidth: addressWidth === 16 || addressWidth === 32 ? addressWidth : 0,
    maxPacketLength: Number.isFinite(maxPacketLength) && maxPacketLength > 0
      ? Math.round(maxPacketLength)
      : 0,
    memoryEndian: memoryEndian === 'little' ? 'little' : (memoryEndian === 'big' ? 'big' : '')
  }
}

async function readMemory(area, startAddress, byteLength, label, kind, options = {}) {
  const transferOptions = normalizeTransferOptions(options)
  const normalizedArea = storageAccessProtocol.normalizeMemoryArea(area)
  const bytes = []
  const chunks = storageAccessProtocol.getReadChunks(startAddress, byteLength, {
    ...transferOptions,
    area: normalizedArea
  })

  for (const chunk of chunks) {
    const response = await transport.sendManagedFrame(
      storageAccessProtocol.buildReadFrame(normalizedArea, chunk.address, chunk.quantity, {
        maxFrameBytes: transferOptions.maxFrameBytes
      }),
      storageAccessProtocol.getChunkLabel(label, chunks, chunk),
      storageAccessProtocol.createExpected(normalizedArea, chunk.address, chunk.quantity, false, kind),
      {
        maxFrameBytes: transferOptions.maxFrameBytes,
        showModal: transferOptions.showModal
      }
    )
    if (!response) return null

    const dataBytes = Array.isArray(response.dataBytes) ? response.dataBytes : []
    dataBytes.forEach((byte, index) => {
      bytes[chunk.address - startAddress + index] = Number(byte) & 0xFF
    })

    if (typeof transferOptions.onChunk === 'function') {
      transferOptions.onChunk(response, chunk)
    }
  }

  return bytes
}

async function writeMemory(area, startAddress, bytes, label, kind, options = {}) {
  const transferOptions = normalizeTransferOptions(options)
  const normalizedArea = storageAccessProtocol.normalizeMemoryArea(area)
  const chunks = storageAccessProtocol.getWriteChunks(startAddress, bytes, {
    ...transferOptions,
    area: normalizedArea
  })

  for (const chunk of chunks) {
    const response = await transport.sendManagedFrame(
      storageAccessProtocol.buildWriteFrame(normalizedArea, chunk.address, chunk.dataBytes, {
        maxFrameBytes: transferOptions.maxFrameBytes
      }),
      storageAccessProtocol.getChunkLabel(label, chunks, chunk),
      storageAccessProtocol.createExpected(normalizedArea, chunk.address, chunk.quantity, true, kind),
      {
        maxFrameBytes: transferOptions.maxFrameBytes,
        showModal: transferOptions.showModal
      }
    )
    if (!response) return false

    if (typeof transferOptions.onChunk === 'function') {
      transferOptions.onChunk(response, chunk)
    }
  }

  return true
}

async function readCodeInfoBlock(label, kind, options = {}) {
  const transferOptions = normalizeTransferOptions({
    ...options,
    useDeviceCaps: false
  })
  const descriptorBytes = await readMemory(
    storageAccessProtocol.AREA.CODEINFO,
    storageAccessProtocol.CODE_INFO_DESCRIPTOR_ADDRESS,
    storageAccessProtocol.CODE_INFO_DESCRIPTOR_BYTE_LENGTH,
    label,
    `${kind}-descriptor`,
    {
      ...transferOptions,
      useDeviceCaps: false,
      showModal: false
    }
  )
  if (!descriptorBytes) {
    if (transferOptions.showModal !== false) {
      transport.showCommandAlert(label, 'CodeInfo 描述符读取失败或超时')
    }
    return null
  }

  let descriptorResponse
  try {
    descriptorResponse = storageAccessProtocol.parseCodeInfoDescriptorBytes(descriptorBytes)
  } catch (error) {
    if (transferOptions.showModal !== false) {
      transport.showCommandAlert(label, error.message || 'CodeInfo 描述符长度无效')
    }
    return null
  }

  const codeInfoAddress = Number(descriptorResponse.codeInfoAddress || 0)
  const codeInfoByteLength = Number(descriptorResponse.codeInfoByteLength || 0)
  let codeInfoAddressWidth
  try {
    codeInfoAddressWidth = storageAccessProtocol.normalizeDescriptorAddressWidth(
      descriptorResponse.codeInfoAddressWidth
    )
  } catch (error) {
    if (transferOptions.showModal !== false) {
      transport.showCommandAlert(label, error.message || 'CodeInfo 描述符地址长度无效')
    }
    return null
  }
  const codeInfoMemoryEndian = String(descriptorResponse.codeInfoMemoryEndian || 'big').trim()
  const codeInfoMaxPacketLength = Number(descriptorResponse.codeInfoMaxPacketLength || 0) & 0xFFFF
  const codeInfoArea = codeInfoAddressWidth === 32 ? storageAccessProtocol.AREA.ADDR32 : storageAccessProtocol.AREA.CODE
  const codeInfoReadAddress = codeInfoAddressWidth === 32 ? codeInfoAddress : (codeInfoAddress & 0xFFFF)
  const codeInfoMaxFrameBytes = storageAccessProtocol.resolveDescriptorMaxFrameBytes(
    transferOptions.maxFrameBytes,
    codeInfoMaxPacketLength
  )
  if (!codeInfoByteLength || codeInfoByteLength > 0xFFFF) {
    if (transferOptions.showModal !== false) {
      transport.showCommandAlert(label, 'CodeInfo 信息块长度无效')
    }
    return null
  }

  const codeInfoBytes = await readMemory(
    codeInfoArea,
    codeInfoReadAddress,
    codeInfoByteLength,
    label,
    kind,
    {
      ...transferOptions,
      maxFrameBytes: codeInfoMaxFrameBytes,
      useDeviceCaps: false
    }
  )
  if (!codeInfoBytes) return null

  return {
    codeInfoAddress,
    codeInfoAddressWidth,
    codeInfoByteLength,
    codeInfoBytes,
    codeInfoDescriptorBytes: descriptorBytes,
    codeInfoMaxPacketLength,
    codeInfoMemoryEndian,
    codeInfoMemoryEndianMark: Number(descriptorResponse.codeInfoMemoryEndianMark || 0) & 0xFFFF,
    codeInfoMemoryType: codeInfoArea
  }
}

async function executeControl(operation, dataBytes, label, kind, options = {}) {
  const transferOptions = normalizeTransferOptions(options)
  const response = await transport.sendManagedFrame(
    storageAccessProtocol.buildControlFrame(operation, dataBytes),
    label,
    storageAccessProtocol.createControlExpected(operation, kind, {
      expectedByteLength: transferOptions.expectedByteLength
    }),
    {
      maxFrameBytes: transferOptions.maxFrameBytes,
      showModal: transferOptions.showModal
    }
  )

  return response || null
}

function formatDwordHex(value) {
  const numberValue = Math.max(0, Math.floor(Number(value) || 0))

  return numberValue.toString(16).toUpperCase().padStart(8, '0')
}

function getAreaAddressWidth(area) {
  return Number(area && area.addressWidth) === 32 || Number(area && area.key) === storageAccessProtocol.AREA.ADDR32
    ? 32
    : 16
}

function getMemoryAreaOptions() {
  return MEMORY_AREAS.map((area) => ({ ...area }))
}

function resolveMemoryCommand(index) {
  const commandIndex = Number(index) === 2 ? MEMORY_WRITE_INDEX : Number(index)

  return {
    command: MEMORY_COMMANDS[commandIndex] || MEMORY_COMMANDS[MEMORY_READ_INDEX],
    index: commandIndex === MEMORY_WRITE_INDEX ? MEMORY_WRITE_INDEX : MEMORY_READ_INDEX
  }
}

function resolveMemoryArea(index, commandKey = 'read') {
  const memoryAreas = getMemoryAreaOptions()
  let areaIndex = Number(index) || 0

  if (commandKey === 'write' && memoryAreas[areaIndex] && memoryAreas[areaIndex].readOnly) {
    areaIndex = 0
  }
  if (!memoryAreas[areaIndex]) areaIndex = 0

  return {
    area: memoryAreas[areaIndex] || memoryAreas[0],
    index: areaIndex,
    options: memoryAreas
  }
}

function normalizeDisplayHexText(value, addressWidth = 16) {
  const text = addressWidth === 32
    ? normalizeHexDwordText(value)
    : normalizeHexWordText(value)

  return text.toUpperCase()
}

function buildMemoryPreview(commandKey, area, address, length, dataBytes) {
  try {
    const frameOptions = {
      maxFrameBytes: 0
    }

    if (commandKey === 'write') {
      return bytesToHex(storageAccessProtocol.buildWriteFrame(area, address, dataBytes, frameOptions), ' ')
    }

    return bytesToHex(storageAccessProtocol.buildReadFrame(area, address, length, frameOptions), ' ')
  } catch (error) {
    return ''
  }
}

function normalizeMemoryCommandState(current = {}, changed = {}) {
  const next = {
    storageAccessAreaIndex: current.storageAccessAreaIndex || 0,
    storageAccessAddress: current.storageAccessAddress || '',
    storageAccessCommandIndex: current.storageAccessCommandIndex || 0,
    storageAccessDataText: current.storageAccessDataText || '',
    storageAccessLength: current.storageAccessLength || '',
    ...changed
  }

  const resolvedCommand = resolveMemoryCommand(next.storageAccessCommandIndex)
  const commandIndex = resolvedCommand.index
  const command = resolvedCommand.command
  const resolvedArea = resolveMemoryArea(next.storageAccessAreaIndex, command.key)
  const areaIndex = resolvedArea.index
  const area = resolvedArea.area
  const isCodeInfoDescriptor = command.key === 'read' && area.key === storageAccessProtocol.AREA.CODEINFO
  const addressWidth = getAreaAddressWidth(area)
  const addressText = isCodeInfoDescriptor
    ? '0000'
    : normalizeDisplayHexText(next.storageAccessAddress, addressWidth)
  const lengthText = isCodeInfoDescriptor
    ? storageAccessProtocol.CODE_INFO_DESCRIPTOR_BYTE_LENGTH.toString(16).toUpperCase().padStart(4, '0')
    : normalizeDisplayHexText(next.storageAccessLength, 16)
  const dataText = normalizeHexText(next.storageAccessDataText)
  const dataBytes = dataText ? parseHexBytes(dataText) : []
  const address = parseInt(addressText || '0', 16)
  const byteLength = parseInt(lengthText || '0', 16)
  const hasAddressText = !!addressText.trim()
  const hasLengthText = !!lengthText.trim()

  let errorText = ''
  const addressError = hasAddressText
    ? (addressWidth === 32
      ? validateHexDwordText(addressText, '地址')
      : validateHexWordText(addressText, '地址'))
    : ''
  const lengthError = hasLengthText ? validateHexWordText(lengthText, '长度') : ''
  if (addressError) {
    errorText = addressError
  } else if (lengthError) {
    errorText = lengthError
  } else if (hasLengthText && byteLength <= 0) {
    errorText = '长度必须大于 0'
  } else if (command.key === 'write') {
    const hexError = validateHexText(next.storageAccessDataText)
    if (hexError) {
      errorText = hexError
    } else if (area.readOnly) {
      errorText = `${area.label} 区不可写`
    } else if (dataBytes.length !== byteLength) {
      errorText = `写入长度为 ${byteLength} 字节，当前数据为 ${dataBytes.length} 字节`
    }
  }

  const canPreview = !errorText && hasAddressText && hasLengthText && byteLength > 0
  const previewHex = canPreview
    ? buildMemoryPreview(command.key, area.key, address, byteLength, dataBytes)
    : ''

  return {
    storageAccessAddress: addressText,
    storageAccessAddressMaxLength: addressWidth === 32 ? 8 : 4,
    storageAccessAddressWidthText: `${addressWidth}bit`,
    storageAccessAreaLocked: false,
    storageAccessAreaOptions: resolvedArea.options,
    storageAccessAreaIndex: areaIndex,
    storageAccessAreaLabel: area.label,
    storageAccessCommandIndex: commandIndex,
    storageAccessCommandLabel: command.label,
    storageAccessDataText: dataText,
    storageAccessErrorText: errorText,
    storageAccessLength: lengthText,
    storageAccessPreviewHexText: previewHex,
    storageAccessPreviewText: canPreview
      ? (isCodeInfoDescriptor
        ? 'codeinfo descriptor'
        : `${area.label} 0x${addressWidth === 32 ? formatDwordHex(address) : address.toString(16).toUpperCase().padStart(4, '0')} / ${byteLength} bytes`)
      : '',
    storageAccessShowWriteData: command.key === 'write',
    storageAccessTitleText: `${command.label}命令`
  }
}

function getSyncedDeviceCaps() {
  return {
    ...syncedDeviceCaps
  }
}

function parseMemoryCommandInput(data = {}) {
  const state = normalizeMemoryCommandState(data)
  const command = resolveMemoryCommand(state.storageAccessCommandIndex).command
  const area = resolveMemoryArea(state.storageAccessAreaIndex, command.key).area
  const addressWidth = getAreaAddressWidth(area)

  if (state.storageAccessErrorText) {
    throw new Error(state.storageAccessErrorText)
  }
  if (!String(state.storageAccessAddress || '').trim()) {
    throw new Error('地址请输入十六进制')
  }
  if (!String(state.storageAccessLength || '').trim()) {
    throw new Error('长度请输入十六进制')
  }

  return {
    area,
    areaValue: area.key,
    byteLength: parseHexNumber(state.storageAccessLength, '长度', 0xFFFF),
    command,
    commandKey: command.key,
    dataBytes: command.key === 'write' ? parseHexBytes(state.storageAccessDataText || '') : [],
    startAddress: addressWidth === 32
      ? parseHexDword(state.storageAccessAddress, '地址')
      : parseHexNumber(state.storageAccessAddress, '地址', 0xFFFF),
    state
  }
}

async function executeMemoryCommand(data = {}, options = {}) {
  const command = parseMemoryCommandInput(data)

  if (command.commandKey === 'read') {
    const bytes = await readMemory(
      command.areaValue,
      command.startAddress,
      command.byteLength,
      options.label || '存储访问协议读取',
      options.kind || 'communication-storage-read',
      options
    )

    return {
      bytes,
      command,
      ok: !!bytes,
      previewHex: bytes ? bytesToHex(bytes, ' ') : '',
      state: command.state
    }
  }

  const ok = await writeMemory(
    command.areaValue,
    command.startAddress,
    command.dataBytes,
    options.label || '存储访问协议写入',
    options.kind || 'communication-storage-write',
    options
  )

  return {
    command,
    ok,
    state: command.state
  }
}

function getControlCommand(commandKey) {
  return CONTROL_COMMANDS.find((command) => command.key === commandKey) || null
}

function normalizeControlState() {
  return {}
}

function getControlCommands() {
  return CONTROL_COMMANDS.map((command) => ({
    ...command,
    previewHexText: bytesToHex(storageAccessProtocol.buildControlFrame(command.op), ' ')
  }))
}

async function syncCodeInfo(options = {}) {
  const result = await readCodeInfoBlock(
    options.label || '同步CodeInfo',
    options.kind || 'storage-code-info-read',
    {
      maxPacketLength: options.maxPacketLength,
      showModal: options.showModal !== false
    }
  )
  if (!result) {
    return {
      ok: false
    }
  }

  const descriptorCaps = {
    addressWidth: result.codeInfoAddressWidth,
    codeInfoByteLength: result.codeInfoByteLength,
    maxPacketLength: result.codeInfoMaxPacketLength,
    memoryEndian: result.codeInfoMemoryEndian,
    memoryEndianMark: result.codeInfoMemoryEndianMark
  }
  const codeInfo = parseCodeInfo(result.codeInfoBytes, descriptorCaps)
  const importedGroups = createGroupsFromCodeInfo(codeInfo, options)
    .map(cloneImportedGroup)
    .map(normalizeGroup)
  updateSyncedDeviceCaps(descriptorCaps)

  return {
    codeInfoAddress: result.codeInfoAddress,
    codeInfoAddressWidth: result.codeInfoAddressWidth,
    codeInfoAddressText: formatDwordHex(result.codeInfoAddress),
    codeInfoByteLength: result.codeInfoByteLength,
    codeInfoByteLengthText: formatDwordHex(result.codeInfoByteLength),
    codeInfoBytes: result.codeInfoBytes,
    codeInfo,
    codeInfoDescriptorBytes: result.codeInfoDescriptorBytes,
    codeInfoMaxPacketLength: result.codeInfoMaxPacketLength,
    codeInfoMemoryEndian: result.codeInfoMemoryEndian,
    codeInfoMemoryEndianMark: result.codeInfoMemoryEndianMark,
    groupCount: importedGroups.length,
    importedGroups,
    codeInfoMemoryType: result.codeInfoMemoryType,
    ok: true,
    structCount: codeInfo.structCount
  }
}

async function executeControlCommand(commandKey, data = {}, options = {}) {
  const command = getControlCommand(commandKey)
  if (!command) {
    return {
      errorText: '特殊指令无效',
      ok: false
    }
  }

  const response = await executeControl(
    command.op,
    [],
    command.label || '特殊指令',
    `storage-control-${command.key}`,
    {
      maxPacketLength: options.maxPacketLength,
      showModal: options.showModal !== false
    }
  )

  if (!response) {
    return {
      errorText: '指令执行失败或超时',
      ok: false
    }
  }

  return {
    command,
    ok: true,
    response
  }
}

module.exports = {
  AREA: storageAccessProtocol.AREA,
  CONTROL_OP: storageAccessProtocol.CONTROL_OP,
  executeMemoryCommand,
  executeControl,
  executeControlCommand,
  formatDwordHex,
  getControlCommand,
  getControlCommands,
  getMemoryAreaOptions,
  getSyncedDeviceCaps,
  normalizeMemoryCommandState,
  normalizeControlState,
  readCodeInfoBlock,
  readMemory,
  resolveMaxPacketLength,
  syncCodeInfo,
  updateSyncedDeviceCaps,
  writeMemory
}
