import { expect, test, type Page } from '@playwright/test'
import { Ticket04Stack } from './support/ticket04-stack'

const stack = new Ticket04Stack()
test.use({ actionTimeout: 20_000 })
test.beforeAll(async () => { await stack.start(); seed() })
test.afterAll(async () => stack.stop())

async function login(page: Page, username = 'admin') {
  if (page.url().startsWith('http')) await page.evaluate(() => { localStorage.removeItem('battery_detection_auth_token'); localStorage.removeItem('battery_detection_auth_user') })
  await page.goto('/login')
  await page.locator('input[autocomplete="username"]').fill(username)
  await page.locator('input[autocomplete="current-password"]').fill(username === 'admin' ? 'admin123' : 'Report123!')
  await page.getByRole('button', { name: '登录', exact: true }).click()
  await expect(page).toHaveURL(/\/dashboard$/)
}

async function api(page: Page, path: string, method = 'GET', data?: unknown, code = 200) {
  const token = await page.evaluate(() => localStorage.getItem('battery_detection_auth_token'))
  const body = await (await page.request.fetch(`${stack.backendUrl}/system/${path}`, { method, data, headers: { Authorization: `Bearer ${token}` } })).json()
  expect(body.code, `${path}: ${body.msg}`).toBe(code)
  return body
}

const ids = [8901, 8902, 8903, 8904, 8905]
const selections = ids.map(id => ({ testId: id, testBatteryId: id }))
function seed() {
  stack.executeIsolatedSql(`
    INSERT INTO t_battery_group(group_id,group_name,model) VALUES(8901,'曲线验收组','型号');
    INSERT INTO t_test_type(test_type_id,test_type_name) VALUES(8901,'化成');
    INSERT INTO t_test(test_id,test_name,creator_id,principal_id,test_type_id,test_purpose,test_status,reception_status)
      VALUES ${ids.map((id, i) => `(${id},'化成测试${i + 1}',1,1,8901,'隔离验收','COMPLETED','ENDED')`).join(',')};
    INSERT INTO t_test_battery(test_battery_id,test_id,group_id,battery_no,collector_id)
      VALUES ${ids.map(id => `(${id},${id},8901,'同名电池',${id})`).join(',')};
    INSERT INTO t_test_realtime_record(test_id,test_battery_id,binding_id,adapter_code,source_identity,source_record_id,cabinet_devtype,cabinet_devid,cabinet_subdevid,cabinet_chlid,cabinet_sequence_id,cabinet_device_time_utc,collector_device_time_utc,server_received_at_utc,cycle_no,step_no,step_name,voltage_v,current_a,temperature,positive_potential,negative_potential,content_sha256)
      VALUES ${ids.flatMap((id, i) => [1, 3].flatMap(cycle => [0, 1, 2].map(point => {
        const seq = cycle * 10 + point
        return `(${id},${id},${id},'formation-curve','formation:${id}','${seq}',27,1,1,${i + 1},${seq},'2026-09-0${i + 1} 0${cycle}:0${point === 2 ? 4 : point}:10.123','2026-09-0${i + 1} 0${cycle}:0${point === 2 ? 4 : point}:00.123','2026-09-0${i + 1} 0${cycle}:0${point === 2 ? 4 : point}:11.123',${cycle},${point + 1},'实际工步',${cycle + i / 10},${point === 1 ? 'NULL' : point === 0 ? '0' : '-8.1651'},${point === 1 ? 'NULL' : '0'},0.1,-0.2,UNHEX(SHA2('${id}:${seq}',256)))`
      }))).join(',')};
    INSERT INTO t_test_step_record(test_id,test_battery_id,binding_id,adapter_code,source_identity,source_record_id,dedupe_key,cycle_no,step_no,step_name,step_type,device_time_utc,server_received_at_utc,adapter_timezone_id,device_time_raw,voltage_v,content_sha256)
      VALUES ${ids.flatMap(id => [1, 3].map(cycle => `(${id},${id},${id},'formation-curve','formation:${id}','${cycle}','${cycle}',${cycle},2,'实际充电','cc','2026-09-01 00:00:00','2026-09-01 00:00:01','Asia/Hong_Kong','2026-09-01 08:00:00',4.123,UNHEX(SHA2('${id}:${cycle}',256)))`)).join(',')};
  `)
}

test('化成每测试电池循环冻结全部实时记录与独立设备时间零点', async ({ page }) => {
  await login(page)
  const matrix = (await api(page, 'formation-report/preview', 'POST', selections)).data
  expect(matrix.cycleNos).toEqual([1, 3])
  expect(matrix.items).toHaveLength(5)
  for (const [index, item] of matrix.items.entries()) {
    for (const cycle of [1, 3]) {
      const curve = item.curves[String(cycle)]
      expect(curve.records).toHaveLength(3)
      expect(curve.zeroTime).toBe(Date.parse(`2026-09-0${index + 1}T0${cycle}:00:00.123Z`))
      expect(curve.records.every((row: any) => row.cycleNo === cycle)).toBe(true)
      expect(curve.records.find((row: any) => row.stepNo === 2).current ?? null).toBeNull()
      expect(curve.records.find((row: any) => row.stepNo === 1).current).toBe(0)
      expect(curve.records.find((row: any) => row.stepNo === 3).current).toBe(-8.1651)
    }
  }
})

const settings = {
  fields: [{ key: 'note', scope: 'detail', kind: 'manual', displayName: '人工值', unit: '' }],
  values: { '8901|8901|0|note': '0' }, chargeStepSelections: {},
  metrics: ['voltage', 'current', 'positiveVoltage', 'negativeVoltage', 'temperature'],
  seriesColors: Object.fromEntries(ids.map((id, i) => [`${id}|${id}`, ['#123456', '#3978f6', '#f08a3e', '#109854', '#e33d54'][i]])),
}

test('化成曲线版本保存、普通编辑、主动更新与指定版本打印权限', async ({ page }) => {
  await login(page)
  const payload = { name: '化成曲线V1', description: '说明 <script>不可执行</script>', selections, settings }
  const v1 = (await api(page, 'formation-report', 'POST', payload)).data
  expect(v1.settings.metrics).toEqual(settings.metrics)
  expect(v1.settings.seriesColors).toEqual(settings.seriesColors)
  expect(v1.items[0].curves['1'].records).toHaveLength(3)
  const bad = { ...payload, settings: { ...settings, seriesColors: { '8901|8901': 'red' } } }
  await api(page, 'formation-report', 'POST', bad, 500)
  stack.executeIsolatedSql('UPDATE t_test_realtime_record SET current_a=99 WHERE test_id=8901 AND cycle_no=1; UPDATE t_test_step_record SET voltage_v=5 WHERE test_id=8901')
  const v2 = (await api(page, `formation-report/${v1.id}/versions`, 'POST', { name: '化成曲线V2', description: '', baseVersionNo: 1, changeSummary: '隐藏温度', settings: { ...settings, metrics: settings.metrics.filter(key => key !== 'temperature') } })).data
  expect(v2.items).toEqual(v1.items)
  const v3 = (await api(page, `formation-report/${v1.id}/refresh-source`, 'POST', { baseVersionNo: 2, changeSummary: '重读当前来源' })).data
  expect(v3.items[0].curves['1'].records.every((row: any) => row.current === 99)).toBe(true)
  // 只有实时记录的其他实际工步也会保留；按实际工步身份核对修订终止值，缺失不借值。
  expect(v3.items[0].chargeSteps['1'].find((step: any) => step.stepNo === 2).endVoltage).toBe(5)
  expect(v3.items[0].chargeSteps['1'].filter((step: any) => step.stepNo !== 2).every((step: any) => step.endVoltage === null)).toBe(true)
  await api(page, 'test/8901', 'DELETE')
  for (const saved of [v1, v2, v3]) {
    expect((await api(page, `formation-report/${v1.id}/versions/${saved.versionNo}/print`)).data).toEqual(saved)
  }
  const permissions = (await api(page, 'menu/permissionList')).data.flatMap((module: any) => module.pages).find((item: any) => item.pageName === '化成报表').permissions
  for (const [username, action] of [['fc_reader', '查看'], ['fc_exporter', '导出']]) {
    const menuIds = permissions.filter((item: any) => item.menuName === action).map((item: any) => item.menuId)
    expect(menuIds).toHaveLength(1)
    await api(page, 'role', 'POST', { roleName: username, roleKey: 'common', roleSort: 1, status: '0', menuIds })
    const role = (await api(page, `role/list?roleName=${username}`)).rows[0]
    await api(page, 'user', 'POST', { userName: username, nickName: username, password: 'Report123!', status: '0', roleIds: [role.roleId] })
  }
  await login(page, 'fc_reader')
  await api(page, `formation-report/${v1.id}/versions/1/print`, 'GET', undefined, 403)
  await login(page, 'fc_exporter')
  expect((await api(page, `formation-report/${v1.id}/versions/1/print`)).data).toEqual(v1)
  await api(page, `formation-report/${v1.id}`, 'GET', undefined, 403)
})

test('历史整循环化成页面保留五类四条曲线、版本设置和多页打印', async ({ page }) => {
  await login(page)
  const pageErrors: string[] = []
  page.on('pageerror', error => pageErrors.push(error.message))
  await page.goto('/report-formation/new')
  await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click()
  await expect(page.getByText('请先保存报表后再打印', { exact: true })).toBeVisible()
  const created = (await api(page, 'formation-report', 'POST', { name: '页面化成打印', description: '', selections: selections.slice(1), settings: { ...settings, values: {}, seriesColors: { '8902|8902': '#123456' } } })).data
  // 整循环展示已属于历史契约；隔离库保留旧快照事实，新增工步验收见 formation-step-curves。
  stack.executeIsolatedSql(`UPDATE t_formation_report_version SET snapshot_json=JSON_REMOVE(snapshot_json,'$.curveMode',${[0, 1, 2, 3].map(index => `'$.items[${index}].stepCurves'`).join(',')},'$.settings.manualChargeSteps','$.settings.curveStepSelections','$.settings.hiddenSeriesKeys','$.settings.activeCycle') WHERE report_id=${created.id}`)
  const id = created.id
  await page.goto(`/report-formation/${id}/edit?mode=view`)
  await expect(page.locator('.chart-card')).toHaveCount(5)
  await expect(page.locator('.chart-legend-item')).toHaveCount(4)
  await expect(page.locator('.chart-legend-bar')).toContainText('化成测试5 · 同名电池')
  await page.locator('.curve-cycle-select').click()
  await page.getByRole('option', { name: '循环3', exact: true }).click()
  const v1 = (await api(page, `formation-report/${id}`)).data
  expect(v1.settings.seriesColors['8902|8902']).toBe('#123456')
  expect(v1.items).toHaveLength(4)
  const plotted = await page.evaluate(async (record) => {
    const path = '/src/utils/formationCycleChart.ts'
    const { formationChartOption, formationCurveMetrics } = await import(path)
    return [1, 3].map(cycle => formationCurveMetrics.map((metric: any) => formationChartOption(metric, record.items, cycle, record.settings.seriesColors)))
  }, v1)
  for (const cycle of plotted) {
    expect(cycle[1].series).toHaveLength(4)
    expect(cycle[1].series[0].data).toEqual([[10, 0], [70, null], [250, -8165.1]])
    expect(cycle[2].series[0].data).toEqual([[0, 0.1], [60, 0.1], [240, 0.1]])
    expect(cycle[3].series[0].data[0][1]).toBe(-0.2)
  }
  expect(plotted[0][0].series.find((series: any) => series.name === '化成测试2 · 同名电池').data[0][1]).toBe(1.1)
  expect(plotted[1][0].series.find((series: any) => series.name === '化成测试2 · 同名电池').data[0][1]).toBe(3.1)
  for (const width of [1440, 390]) {
    await page.setViewportSize({ width, height: 900 })
    await page.locator('.metrics-panel').scrollIntoViewIfNeeded()
    await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
    const coloredPixels = await page.locator('.metric-chart canvas').first().evaluate((canvas: HTMLCanvasElement) => {
      const pixels = canvas.getContext('2d')!.getImageData(0, 0, canvas.width, canvas.height).data
      let count = 0
      for (let i = 0; i < pixels.length; i += 4) if (pixels[i + 3]! > 0 && Math.max(pixels[i]!, pixels[i + 1]!, pixels[i + 2]!) - Math.min(pixels[i]!, pixels[i + 1]!, pixels[i + 2]!) > 50) count++
      return count
    })
    expect(coloredPixels).toBeGreaterThan(100)
    await page.locator('.metrics-panel').evaluate(element => window.scrollTo(0, element.getBoundingClientRect().top + window.scrollY - 76))
    await page.screenshot({ path: `tests/e2e/.artifacts/formation-curves-${width}.png` })
  }
  await page.getByRole('button', { name: '编辑', exact: true }).click()
  await page.locator('.metric-switches .el-checkbox').filter({ hasText: '温度' }).click()
  await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click()
  await expect(page.getByText('有未保存修改，请先保存新版本后再打印', { exact: true })).toBeVisible()
  await page.getByPlaceholder('请输入修改说明').fill('保存显隐设置')
  await page.getByRole('button', { name: '保存新版本', exact: true }).click()
  await expect(page).toHaveURL(/mode=view/)
  await page.reload()
  await expect(page.locator('.chart-card')).toHaveCount(4)
  expect((await api(page, `formation-report/${id}`)).data.items).toEqual(v1.items)
  await page.evaluate(() => { window.open = () => null })
  await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click()
  await expect(page.getByText('打印窗口打开失败，请允许浏览器弹出窗口后重试', { exact: true })).toBeVisible()
  for (const source of ids.slice(1)) await api(page, `test/${source}`, 'DELETE')
  await page.context().addInitScript(() => { window.print = () => { document.body.dataset.printRequested = 'true' } })
  for (const version of [2, 1]) {
    await page.goto(`/report-formation/${id}/edit?mode=view&version=${version}`)
    await expect(page.locator('.chart-card')).toHaveCount(version === 1 ? 5 : 4)
    const popupPromise = page.waitForEvent('popup')
    await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click()
    const popup = await popupPromise
    await expect(popup.locator('body')).toHaveAttribute('data-print-requested', 'true')
    await expect(popup.locator('.saved-meta')).toContainText(`V${version}`)
    await expect(popup.locator('.chart-card')).toHaveCount(version === 1 ? 10 : 8)
    await expect(popup.locator('.legend').first()).toContainText('化成测试5 · 同名电池')
    await expect(popup.locator('[data-cycle="3"] .matrix-table')).toContainText('4.123')
    await expect(popup.locator('.chart-card').first().locator('svg path[stroke="#123456"]').first()).toBeAttached()
    await popup.setViewportSize({ width: 1200, height: 900 })
    await popup.emulateMedia({ media: 'print' })
    expect(await popup.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
    await popup.screenshot({ path: `tests/e2e/.artifacts/formation-print-v${version}.png`, fullPage: true })
    await popup.pdf({ path: `tests/e2e/.artifacts/formation-print-v${version}.pdf`, preferCSSPageSize: true, printBackground: true })
    await popup.close()
  }
  expect((await api(page, `formation-report/${id}/versions`)).data).toHaveLength(2)
  await expect(page.getByText(/下载成功|成功下载/)).toHaveCount(0)
  expect(pageErrors).toEqual([])
})

test('旧版缺少曲线仍可重开打印，普通编辑不补造且主动更新不回填旧版', async ({ page }) => {
  await login(page)
  stack.executeIsolatedSql(`
    INSERT INTO t_test(test_id,test_name,creator_id,principal_id,test_type_id,test_purpose,test_status,reception_status) VALUES(8990,'旧版来源',1,1,8901,'隔离验收','COMPLETED','ENDED');
    INSERT INTO t_test_battery(test_battery_id,test_id,group_id,battery_no,collector_id) VALUES(8990,8990,8901,'旧版电池',8990);
    INSERT INTO t_test_realtime_record(test_id,test_battery_id,binding_id,adapter_code,source_identity,source_record_id,cabinet_devtype,cabinet_devid,cabinet_subdevid,cabinet_chlid,cabinet_sequence_id,cabinet_device_time_utc,collector_device_time_utc,server_received_at_utc,cycle_no,step_no,step_name,voltage_v,current_a,content_sha256)
    VALUES(8990,8990,8990,'legacy','legacy:8990','1',27,1,1,1,1,'2026-09-01 00:00:10','2026-09-01 00:00:00','2026-09-01 00:00:11',1,1,'旧版工步',3.5,0,UNHEX(SHA2('legacy',256)));
  `)
  const created = (await api(page, 'formation-report', 'POST', { name: '旧版化成', description: '', selections: [{ testId: 8990, testBatteryId: 8990 }], settings: { ...settings, values: {}, seriesColors: {} } })).data
  // 仅在隔离库构造早期快照，模拟当时尚未保存曲线与工步选择的报表。
  stack.executeIsolatedSql(`UPDATE t_formation_report_version SET snapshot_json=JSON_REMOVE(snapshot_json,'$.curveMode','$.items[0].stepCurves','$.items[0].curves','$.items[0].chargeSteps','$.settings.metrics','$.settings.seriesColors') WHERE report_id=${created.id}`)
  const old = (await api(page, `formation-report/${created.id}`)).data
  await page.context().addInitScript(() => { window.print = () => { document.body.dataset.printRequested = 'true' } })
  await page.goto(`/report-formation/${created.id}/edit?mode=view`)
  await expect(page.getByText('该版本未保存曲线数据', { exact: true })).toBeVisible()
  const popupPromise = page.waitForEvent('popup')
  await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click()
  const popup = await popupPromise
  await expect(popup.getByText('该版本未保存曲线数据', { exact: true })).toBeVisible()
  await popup.close()
  const edited = (await api(page, `formation-report/${created.id}/versions`, 'POST', { name: '旧版普通编辑', description: '', baseVersionNo: 1, changeSummary: '沿用快照', settings: old.settings })).data
  expect(edited.items).toEqual(old.items)
  const updated = (await api(page, `formation-report/${created.id}/refresh-source`, 'POST', { baseVersionNo: 2, changeSummary: '主动读取曲线' })).data
  expect(updated.items[0].curves['1'].records).toHaveLength(1)
  expect((await api(page, `formation-report/${created.id}/versions/1/print`)).data).toEqual(old)
})
