import assert from 'node:assert/strict'
import { test } from 'node:test'
import XLSX from 'xlsx'
import { parseConsoleImport, consoleImportColumns } from '../src/utils/consoleDataImport.ts'
import * as importUtils from '../src/utils/consoleDataImport.ts'
import { formatTestDuration, parseTestDuration } from '../src/utils/testDataDuration.ts'

test('时长保留毫秒、累计小时和空值，非法时长不变成有效值', () => {
  assert.equal(formatTestDuration(90062.125), '25:01:02.125')
  assert.equal(parseTestDuration('25:01:02.125'), 90062.125)
  assert.equal(formatTestDuration(0), '0:00:00.000')
  assert.equal(formatTestDuration(null), '-')
  assert.equal(parseTestDuration('-'), null)
  for (const value of ['-1', '0:60:00.000', '2023-05-08 12:00:00', 'abc']) assert.ok(Number.isNaN(parseTestDuration(value)))
})

test('工步单页模板借同电池 record 时间，系统编号不充当来源编号', () => {
  const workbook = importUtils.createConsoleImportWorkbook('STEP')
  assert.deepEqual(workbook.SheetNames, ['工步', 'record'])
  assert.deepEqual(XLSX.utils.sheet_to_json(workbook.Sheets['工步'], { header: 1 })[0], [
    '记录编号', '电池', '循环序号', '工步序号', '工步名称', '工步时间(h:min:s.ms)', '容量(mAh)', '能量(mWh)',
    '起始电压(V)', '终止电压(V)', '充电中值电压(V)', '放电中值电压(V)',
  ])
  XLSX.utils.sheet_add_aoa(workbook.Sheets['工步'], [['step:999', 'BAT-1', 1, 1, '恒流放电', '1:02:32.125']], { origin: 'A2' })
  const records = [{ batteryNo: 'BAT-1', recordedAt: '2023-05-08 11:42:11' }, { batteryNo: 'BAT-2', recordedAt: '2023-05-08 12:42:11' }]
  XLSX.utils.sheet_add_aoa(workbook.Sheets.record, records.map(row => Object.keys(consoleImportColumns.REALTIME).map(key => ({ ...row, cycleNo: 1, stepNo: 1 })[key] ?? '')), { origin: 'A2' })
  const imported = parseConsoleImport(workbook, 'STEP', ['BAT-1', 'BAT-2'])
  assert.equal(imported.length, 1)
  assert.deepEqual(imported[0].errors, [])
  assert.equal(imported[0].data.recordNo, 'step:1:1')
  assert.equal(imported[0].data.recordedAt, 1683517331000)
  assert.equal(imported[0].data.stepDuration, 3752.125)
  workbook.Sheets.record.A2 = { t: 's', v: '' }
  workbook.Sheets.record.B2 = { t: 's', v: 'BAT-2' }
  assert.match(parseConsoleImport(workbook, 'STEP', ['BAT-1', 'BAT-2'])[0].errors.join(), /绝对.*无效/)
})

test('一键模板包含三个标准 Sheet，并逐 Sheet 预览与报告缺失表', () => {
  const workbook = importUtils.createConsoleImportWorkbook()
  assert.deepEqual(workbook.SheetNames, ['实时', '工步', '循环'])
  const row = { recordNo: '17', batteryNo: 'BAT-1', recordedAt: '2023-05-08 11:42:11', cycleNo: 1, stepNo: 1, stepName: '恒流放电', sequenceId: 17 }
  for (const [index, type] of ['REALTIME', 'STEP', 'CYCLE'].entries()) {
    const sheet = workbook.Sheets[workbook.SheetNames[index]]
    assert.deepEqual(XLSX.utils.sheet_to_json(sheet, { header: 1 })[0], Object.values(consoleImportColumns[type]))
    XLSX.utils.sheet_add_aoa(sheet, [Object.keys(consoleImportColumns[type]).map(key => row[key] ?? '')], { origin: 'A2' })
  }
  const sheets = importUtils.parseConsoleImportWorkbook(workbook, ['BAT-1'])
  assert.deepEqual(sheets.map(sheet => [sheet.type, sheet.rows.length, sheet.errors]), [['REALTIME', 1, []], ['STEP', 1, []], ['CYCLE', 1, []]])
  delete workbook.Sheets['循环']
  const missing = importUtils.parseConsoleImportWorkbook(workbook, ['BAT-1'])
  assert.match(missing[2].errors.join(), /缺少循环工作表/)
  assert.equal(missing[0].rows.length, 1)
})

function partyWorkbook() {
  const workbook = XLSX.utils.book_new()
  XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet([['设备信息']]), 'Unit')
  XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet([
    ['循环序号', '工步序号', '工步名称', '记录序号', '记录时间(h:min:s.ms)', '电压(V)', '电流(mA)', '容量(mAh)', '能量(mWh)', '绝对时间'],
    [1, 1, '恒流放电', 1, '0:00:00.000', 1.3349, -8165.1, 0, 0, '2023-05-08 11:42:11'],
    [1, 1, '恒流放电', 2, '1:02:32.000', 0.8999, -8165.1, 8512.2, 9717.8, '2023-05-08 12:44:43'],
  ]), 'record')
  return workbook
}

test('实时子页选择 record 并保留身份、香港绝对时间及系统单位', () => {
  const rows = parseConsoleImport(partyWorkbook(), 'REALTIME', ['BAT-1'], 'BAT-1')
  assert.equal(rows.length, 2)
  assert.deepEqual(rows[0].errors, [])
  assert.equal(rows[0].data.recordNo, '1')
  assert.equal(rows[0].data.sequenceId, 1)
  assert.equal(rows[0].data.cycleNo, 1)
  assert.equal(rows[0].data.stepNo, 1)
  assert.equal(rows[0].data.recordedAt, 1683517331000)
  assert.equal(rows[0].data.relativeTimeSeconds, 0)
  assert.equal(rows[1].data.relativeTimeSeconds, 3752)
  assert.equal(rows[0].data.deviceTimeRaw, '2023-05-08 11:42:11')
  assert.equal(rows[0].data.current, -8.1651)
  assert.equal(rows[1].data.capacity, 8.5122)
  assert.equal(rows[1].data.energy, 9.7178)
  for (const field of ['temperature', 'positivePotential', 'negativePotential']) {
    assert.equal(rows[0].data[field], null)
  }
})

test('汇总时间匹配组中存在非法绝对时间时拒绝，不选择更早有效行代替', () => {
  const workbook = partyWorkbook()
  XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet([
    Object.values(consoleImportColumns.CYCLE), ['', 'BAT-1', 1, 0, 0, 0, 0, 0],
  ]), '循环')
  workbook.Sheets.record.J3 = { t: 's', v: '错误时间' }
  assert.match(parseConsoleImport(workbook, 'CYCLE', ['BAT-1'])[0].errors.join(), /绝对.*无效/)
})

test('采集器三项测量只读取 Excel 已有值，缺列或空格不生成数值', () => {
  const workbook = partyWorkbook()
  XLSX.utils.sheet_add_aoa(workbook.Sheets.record, [
    ['温度(℃)', '正电位(V)', '负电位(V)'],
    [32.75, 1.8234, -0.2045],
    ['', '', ''],
  ], { origin: 'K1' })
  const rows = parseConsoleImport(workbook, 'REALTIME', ['BAT-1'])
  assert.deepEqual(rows[0].errors, [])
  assert.deepEqual(['temperature', 'positivePotential', 'negativePotential'].map(field => rows[0].data[field]), [32.75, 1.8234, -0.2045])
  assert.deepEqual(['temperature', 'positivePotential', 'negativePotential'].map(field => rows[1].data[field]), [null, null, null])
})

test('标准模板兼容文本、毫秒时间戳和 Excel 日期系统，并拒绝非法日期', () => {
  const make = (time, date1904 = false) => {
    const workbook = XLSX.utils.book_new()
    workbook.Workbook = { WBProps: { date1904 } }
    const data = { recordNo: '17', batteryNo: 'BAT-1', recordedAt: time, cycleNo: 1, stepNo: 1, stepName: '恒流放电', sequenceId: 17 }
    XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet([
      Object.values(consoleImportColumns.REALTIME), Object.keys(consoleImportColumns.REALTIME).map(field => data[field] ?? ''),
    ]), '实时模板')
    return parseConsoleImport(workbook, 'REALTIME', ['BAT-1'])[0]
  }
  assert.equal(make('2023/5/8 11:42:11.123').data.recordedAt, 1683517331123)
  assert.equal(make(1683517331123).data.recordedAt, 1683517331123)
  assert.equal(make(45054).data.recordedAt, 1683475200000)
  assert.equal(make(43592, true).data.recordedAt, 1683475200000)
  assert.match(make('2023-02-30 11:00:00').errors.join(), /第2行.*设备时间.*无效/)
  assert.match(make('1:02:32.000').errors.join(), /设备时间.*无效/)
})

test('多电池文件要求明确归属，保留空白行后的实际错误位置', () => {
  assert.match(parseConsoleImport(partyWorkbook(), 'REALTIME', ['BAT-1', 'BAT-2'])[0].errors.join(), /电池.*不能为空/)
  assert.equal(parseConsoleImport(partyWorkbook(), 'REALTIME', ['BAT-1', 'BAT-2'], 'BAT-2')[0].data.batteryNo, 'BAT-2')
  const workbook = partyWorkbook()
  XLSX.utils.sheet_add_aoa(workbook.Sheets.record, [[1, 1, '恒流放电', 19, '0:01:00.000', '不是数值', 0, 0, 0, '2023-05-08 11:44:11']], { origin: 'A20' })
  const last = parseConsoleImport(workbook, 'REALTIME', ['BAT-1']).at(-1)
  assert.equal(last.sourceRow, 20)
  assert.match(last.errors.join(), /第20行，列「电压\(V\)」/)
})

test('工步和循环仅转换对应页，时间取匹配 record 的末次绝对时间', () => {
  const workbook = partyWorkbook()
  XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet([
    ['循环序号', '工步序号', '工步名称', '工步时间(h:min:s.ms)', '容量(mAh)', '能量(mWh)', '起始电压(V)', '终止电压(V)', '充电中值电压(V)', '放电中值电压(V)'],
    [1, 1, '恒流放电', '1:02:32.000', 8512.2, 9717.8, 1.3349, 0.8999, 0, 1.1709],
  ]), 'step')
  XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet([
    ['循环序号', '充电容量(mAh)', '放电容量(mAh)', '充放电效率(%)', '充电能量(mWh)', '放电能量(mWh)'],
    [1, 0, 8512.2, 100, 0, 9717.8],
  ]), 'cycle')
  const step = parseConsoleImport(workbook, 'STEP', ['BAT-1'])[0]
  assert.deepEqual(step.errors, [])
  assert.equal(step.data.recordNo, 'step:1:1')
  assert.equal(step.data.recordedAt, 1683521083000)
  assert.equal(step.data.stepDuration, 3752)
  assert.equal(step.data.chargeMedianVoltage, 0)
  assert.equal(step.data.dischargeMedianVoltage, 1.1709)
  // 中值空格和缺列不变成零，也不回退读取同一文件的峰谷值。
  XLSX.utils.sheet_add_aoa(workbook.Sheets.step, [['', '', '充电峰值电压(V)', '放电谷值电压(V)'], ['', '', 1.8, 0.8]], { origin: 'I1' })
  const absent = parseConsoleImport(workbook, 'STEP', ['BAT-1'])[0]
  assert.deepEqual(absent.errors, [])
  assert.equal(absent.data.chargeMedianVoltage, null)
  assert.equal(absent.data.dischargeMedianVoltage, null)
  XLSX.utils.sheet_add_aoa(workbook.Sheets.step, [['充电中值电压(V)', '放电中值电压(V)'], ['', '不是数字']], { origin: 'I1' })
  const invalid = parseConsoleImport(workbook, 'STEP', ['BAT-1'])[0]
  assert.equal(invalid.data.chargeMedianVoltage, null)
  assert.match(invalid.errors.join(), /放电中值电压.*必须是数字或空值/)
  const cycle = parseConsoleImport(workbook, 'CYCLE', ['BAT-1'])[0]
  assert.deepEqual(cycle.errors, [])
  assert.equal(cycle.data.recordNo, 'cycle:1')
  assert.equal(cycle.data.recordedAt, 1683521083000)
  assert.equal(cycle.data.dischargeCapacity, 8.5122)
  delete workbook.Sheets.record
  assert.match(parseConsoleImport(workbook, 'STEP', ['BAT-1'])[0].errors.join(), /第2行.*绝对时间.*无效/)
})
