import { expect, test, type Page } from '@playwright/test'
import { readFile } from 'node:fs/promises'
import { Ticket04Stack } from './support/ticket04-stack'

const stack = new Ticket04Stack()
test.use({ actionTimeout: 20_000 })
test.beforeAll(async () => stack.start())
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 response = await page.request.fetch(`${stack.backendUrl}/system/${path}`, {
    method, data, headers: { Authorization: `Bearer ${token}` },
  })
  const body = await response.json()
  expect(body.code, `${path}: ${body.msg}`).toBe(code)
  return body
}

test('指定版本打印独立于来源，导出权限由正式 API 执行', async ({ page }) => {
  stack.executeIsolatedSql(`
    INSERT INTO t_battery_group(group_id,group_name,model) VALUES(8301,'打印验收组','型号');
    INSERT INTO t_battery_group_field(field_id,field_name,field_type) VALUES(8301,'质量','number');
    INSERT INTO t_battery_group_relation_field(group_id,battery_group_field_id,field_value) VALUES(8301,8301,'88');
    INSERT INTO t_test_type(test_type_id,test_type_name) VALUES(8301,'打印验收类型');
    INSERT INTO t_test(test_id,test_name,creator_id,principal_id,test_type_id,test_purpose,test_status,reception_status)
    VALUES(8301,'打印来源同名电池长测试名称用于检查图例完整换行',1,1,8301,'隔离验收','COMPLETED','ENDED');
    INSERT INTO t_test_battery(test_battery_id,test_id,group_id,battery_no,collector_id) VALUES(8301,8301,8301,'BAT-P',8301);
    INSERT INTO t_detection_cabinet(cabinet_id,adapter_code,cabinet_code,cabinet_name,vendor_device_type,vendor_device_id)
    VALUES(8301,'print-fixture','print-fixture','验收检测柜',27,1);
    INSERT INTO t_detection_channel(channel_id,adapter_code,cabinet_id,devtype,devid,subdevid,chlid) VALUES(8301,'print-fixture',8301,27,1,1,1);
    INSERT INTO t_test_channel_binding(binding_id,test_id,test_battery_id,channel_id) VALUES(8301,8301,8301,8301);
    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 ${[1, 2, 3, 4, 5].flatMap(cycle => [0, 1, 2].map(point => {
      const seq = cycle * 10 + point
      return `(8301,8301,8301,'print-fixture','print:27:1:1:1','${seq}',27,1,1,1,${seq},'2026-09-01 0${cycle}:0${point}:10.123','2026-09-01 0${cycle}:0${point}:00.123','2026-09-01 0${cycle}:0${point}:11.123',${cycle},1,'充电',${3.5 + cycle / 10},${point === 1 ? 'NULL' : '-8.1651'},${point === 0 ? '0' : '25'},0.1,-0.2,UNHEX(SHA2('${seq}',256)))`
    })).join(',')};
  `)
  await login(page)
  const selections = [1, 2, 3, 4, 5].map(cycleNo => ({ testId: 8301, testBatteryId: 8301, cycleNo, stepNo: 1, logicalNo: null }))
  const payload = { name: '指定打印V1', description: '第一版说明 <script>不可执行</script>', experimentName: '打印实验', selections,
    settings: { metrics: ['voltage', 'current', 'temperature', 'positiveVoltage', 'negativeVoltage'],
      seriesColors: ['#123456', '#3978f6', '#f08a3e', '#109854', '#e33d54'], batteryTemplateFields: ['质量'],
      specialFields: Array.from({ length: 36 }, (_, index) => `人工指标${index + 1}`),
      detailFieldValues: { 人工指标1: { '8301|8301|1|1|': '0', '8301|8301|2|1|': '-12' } } },
  }
  const v1 = (await api(page, 'compare-report', 'POST', payload)).data
  const v2 = (await api(page, `compare-report/${v1.id}/versions`, 'POST', {
    name: '指定打印V2', description: '当前版说明', experimentName: payload.experimentName,
    settings: payload.settings, baseVersionNo: 1, changeSummary: '第二版真实修订说明',
  })).data
  await api(page, 'test/8301', 'DELETE')
  for (const saved of [v1, v2]) {
    expect((await api(page, `compare-report/${v1.id}/versions/${saved.versionNo}/print`)).data).toEqual(saved)
  }
  const menus = (await api(page, 'menu/permissionList')).data
  const comparePage = menus.flatMap((module: any) => module.pages).find((item: any) => item.pageName === '对比报表')
  expect(comparePage.permissions.map((item: any) => item.menuName)).toContain('导出')
  for (const [username, actions] of [['print_reader', ['查看']], ['print_exporter', ['导出']]] as const) {
    const menuIds = comparePage.permissions.filter((item: any) => (actions as readonly string[]).includes(item.menuName)).map((item: any) => item.menuId)
    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, 'print_reader')
  await api(page, `compare-report/${v1.id}/versions/1/print`, 'GET', undefined, 403)
  await api(page, `compare-report/${v1.id}/versions/export`, 'GET', undefined, 403)
  await page.goto(`/report-compare/${v1.id}/edit?mode=view`)
  await expect(page.getByPlaceholder('请输入报表名称')).toHaveValue('指定打印V2')
  await expect(page.getByRole('button', { name: '打印 / 另存 PDF', exact: true })).toHaveCount(0)
  await login(page, 'print_exporter')
  expect((await api(page, `compare-report/${v1.id}/versions/1/print`)).data).toEqual(v1)
  const history = (await api(page, `compare-report/${v1.id}/versions/export`)).data
  expect(history.map((version: any) => version.versionNo)).toEqual([2, 1])
  expect(history[0].changeSummary).toBe('第二版真实修订说明')

  await login(page)
  const pageErrors: string[] = []
  page.on('pageerror', error => pageErrors.push(error.message))
  await page.goto('/report-compare/new')
  await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click()
  await expect(page.getByText('请先保存报表后再打印', { exact: true })).toBeVisible()
  await page.goto(`/report-compare/${v1.id}/edit?mode=edit`)
  await expect(page.getByPlaceholder('请输入报表名称')).toHaveValue('指定打印V2')
  await page.getByPlaceholder('请输入报表名称').fill('未保存草稿')
  let printRequests = 0
  page.on('request', request => { if (request.url().endsWith('/print')) printRequests++ })
  await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click()
  await expect(page.getByText('有未保存修改，请先保存新版本后再打印', { exact: true })).toBeVisible()
  expect(printRequests).toBe(0)
  await page.getByRole('button', { name: '取消编辑', exact: true }).click()
  await expect(page.getByPlaceholder('请输入报表名称')).toHaveValue('指定打印V2')
  await page.evaluate(() => { window.open = () => null })
  await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click()
  await expect(page.getByText('打印窗口打开失败，请允许浏览器弹出窗口后重试', { exact: true })).toBeVisible()
  expect(printRequests).toBe(0)
  await page.reload()
  await expect(page.getByPlaceholder('请输入报表名称')).toHaveValue('指定打印V2')
  // 模拟打印对话框返回（包括取消），不得据此追加版本或下载成功事实。
  await page.context().addInitScript(() => { window.print = () => { document.body.dataset.printRequested = 'true' } })
  await page.route('**/dev-api/system/compare-report/*/versions/*/print', async route => {
    await route.fulfill({ json: { code: 403, msg: '打印导出权限已撤销' } })
  }, { times: 1 })
  const deniedPopupPromise = page.waitForEvent('popup')
  await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click()
  const deniedPopup = await deniedPopupPromise
  await expect(page.getByText('打印导出权限已撤销', { exact: true })).toBeVisible()
  await expect.poll(() => deniedPopup.isClosed()).toBe(true)
  let blockedSourceRequests = 0
  await page.route('**/dev-api/system/test/**', async route => { blockedSourceRequests++; await route.abort() })
  for (const version of [2, 1]) {
    await page.goto(`/report-compare/${v1.id}/edit?mode=view${version === 1 ? '&version=1' : ''}`)
    await expect(page.getByPlaceholder('请输入报表名称')).toHaveValue(`指定打印V${version}`)
    for (const width of [1440, 390]) {
      await page.setViewportSize({ width, height: 900 })
      const button = page.getByRole('button', { name: '打印 / 另存 PDF', exact: true })
      await button.scrollIntoViewIfNeeded()
      await expect(button).toBeInViewport()
      await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
      await page.screenshot({ path: `tests/e2e/.artifacts/compare-print-entry-v${version}-${width}.png`, fullPage: true })
    }
    const popupPromise = page.waitForEvent('popup')
    await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click()
    const popup = await popupPromise
    await expect(popup.locator('h1')).toHaveText(`指定打印V${version}`)
    await expect(popup.locator('.saved-meta')).toContainText(`V${version}`)
    await expect(popup.locator('.compare-table tbody tr')).toHaveCount(5)
    await expect(popup.locator('.chart-card')).toHaveCount(5)
    await expect(popup.locator('.legend')).toContainText('循环5')
    await expect(popup.locator('.chart-inner-legend')).toHaveCount(5)
    await expect(popup.locator('body')).toHaveAttribute('data-print-requested', 'true')
    await expect(popup.locator('.detail-table tbody tr').filter({ hasText: '质量' }).first()).toContainText('88')
    const manualRow = popup.locator('.detail-table tbody tr').filter({ has: popup.locator('td:first-child', { hasText: /^人工指标1$/ }) }).first()
    await expect(manualRow.locator('td').nth(1)).toHaveText('0')
    await expect(manualRow.locator('td').nth(2)).toHaveText('-12')
    await expect(popup.locator('.chart-card').filter({ hasText: '电流' })).toContainText('mA')
    for (const color of payload.settings.seriesColors) {
      await expect(popup.locator('.chart-card').first().locator(`svg path[stroke="${color}"]`).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/compare-print-v${version}.png`, fullPage: true })
    await popup.pdf({ path: `tests/e2e/.artifacts/compare-print-v${version}.pdf`, preferCSSPageSize: true, printBackground: true })
    await popup.close()
  }
  expect(blockedSourceRequests).toBe(0)
  expect((await api(page, `compare-report/${v1.id}/versions`)).data).toEqual(history)
  await expect(page.getByText(/下载成功|成功下载/)).toHaveCount(0)
  await page.getByRole('button', { name: '历史版本', exact: true }).click()
  const downloadPromise = page.waitForEvent('download')
  await page.getByRole('button', { name: '导出修订 CSV', exact: true }).click()
  const download = await downloadPromise
  expect(download.suggestedFilename()).toBe(`对比报表-${v1.id}-修订记录.csv`)
  const csv = await readFile((await download.path())!, 'utf8')
  expect(csv).toContain('版本号,操作人,时间,修改说明')
  expect(csv).toContain('第二版真实修订说明')
  expect(csv).toContain('admin')
  expect(csv).not.toContain('PDF')
  const v3 = (await api(page, `compare-report/${v1.id}/versions`, 'POST', {
    name: '隐藏全部曲线V3', description: '', experimentName: '', settings: { ...payload.settings, metrics: [] },
    baseVersionNo: 2, changeSummary: '关闭所有指标',
  })).data
  await page.goto(`/report-compare/${v1.id}/edit?mode=view`)
  await expect(page.getByPlaceholder('请输入报表名称')).toHaveValue(v3.name)
  const emptyPopupPromise = page.waitForEvent('popup')
  await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click()
  const emptyPopup = await emptyPopupPromise
  await expect(emptyPopup.getByText('当前未启用曲线指标', { exact: true })).toBeVisible()
  await expect(emptyPopup.locator('.chart-card')).toHaveCount(0)
  await emptyPopup.close()
  expect(pageErrors).toEqual([])
})
