/**
 * E2E tests for the Bitable record detail drawer (U3 / R2).
 *
 * Flow: login → create file → create table → seed a record via API →
 *       click the row's seq cell → assert drawer opens with all field types.
 *
 * ponytail: record + extra-field seeding goes through the REST API (via
 * page.evaluate fetch) rather than UI clicks — setup is not the thing under
 * test, and API seeding is ~10x faster + more deterministic than driving the
 * add-field / add-record UI for 11+ fields. Ceiling: API path couples the
 * test to the /tables/{id}/fields + /tables/{id}/records contract; if that
 * contract shifts, setup breaks (acceptable — the same contract is exercised
 * by the grid itself).
 *
 * Requires: running backend with PostgreSQL. Skips gracefully if unreachable.
 */

import { test, expect, type Page } from '@playwright/test'
import { TEST_USER, clearAuth, waitForServer, API_BASE } from './helpers'

interface IField {
  id: string
  name: string
  field_type: string
  owner: string
}

interface IRecord {
  id: string
  values: Record<string, unknown>
}

// API_BASE is an absolute URL (http://127.0.0.1:PORT/api/v1) — passed into
// page.evaluate as an arg because the browser context cannot close over
// Node-scope variables.
const API_BASE_STR = API_BASE

async function loginAndCreateTable(
  page: Page,
  fileName: string,
  tableName: string,
): Promise<{ tableId: string; fields: IField[] }> {
  await page.goto('/login')
  await clearAuth(page)
  await page.getByPlaceholder('请输入用户名').fill(TEST_USER.username)
  await page.getByPlaceholder('请输入密码').fill(TEST_USER.password)
  await page.getByRole('button', { name: /登\s*录/ }).click()
  await expect(page).toHaveURL(/\/agent/, { timeout: 15_000 })

  await page.getByRole('button', { name: '多维表格' }).click()
  await expect(page).toHaveURL(/\/bitable$/, { timeout: 15_000 })

  // Create file
  await page.getByRole('button', { name: /新建文件/ }).click()
  await page.getByPlaceholder('请输入文件名').fill(fileName)
  await page.getByRole('button', { name: /确\s*定/ }).click()
  await expect(page).toHaveURL(/\/bitable\/[^/]+/, { timeout: 10_000 })

  // Create table
  await page.locator('.table-view-list__header .ant-btn').click()
  await page.getByPlaceholder('请输入表名').fill(tableName)
  await page.getByRole('button', { name: /确\s*定/ }).click()
  await expect(page.locator('.bitable-file-detail-view__table-name')).toContainText(
    tableName,
    { timeout: 10_000 },
  )

  // Fetch table id + fields via API (auth token is in localStorage after UI login).
  // apiBase is passed as an arg — browser context cannot close over Node vars.
  const { tableId, fields } = await page.evaluate(async (apiBase: string) => {
    const token = localStorage.getItem('agentkit.access_token') ?? ''
    const tablesResp = await fetch(`${apiBase}/bitable/tables`, {
      headers: { Authorization: `Bearer ${token}` },
    })
    const tablesJson = (await tablesResp.json()) as { tables: { id: string; name: string }[] }
    const table = tablesJson.tables[0]
    const fieldsResp = await fetch(`${apiBase}/bitable/tables/${table.id}/fields`, {
      headers: { Authorization: `Bearer ${token}` },
    })
    const fieldsJson = (await fieldsResp.json()) as { fields: IField[] }
    return { tableId: table.id, fields: fieldsJson.fields }
  }, API_BASE_STR)
  return { tableId, fields }
}

async function createRecordViaApi(
  page: Page,
  tableId: string,
  values: Record<string, unknown>,
): Promise<IRecord> {
  return await page.evaluate(
    async ({ tableId, values, apiBase }) => {
      const token = localStorage.getItem('agentkit.access_token') ?? ''
      const resp = await fetch(`${apiBase}/bitable/tables/${tableId}/records`, {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${token}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ records: [values] }),
      })
      const json = (await resp.json()) as { records: IRecord[] }
      return json.records[0]
    },
    { tableId, values, apiBase: API_BASE_STR },
  )
}

async function createFieldViaApi(
  page: Page,
  tableId: string,
  name: string,
  fieldType: string,
  owner: string = 'user',
): Promise<IField> {
  return await page.evaluate(
    async ({ tableId, name, fieldType, owner, apiBase }) => {
      const token = localStorage.getItem('agentkit.access_token') ?? ''
      const resp = await fetch(`${apiBase}/bitable/tables/${tableId}/fields`, {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${token}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ name, field_type: fieldType, owner }),
      })
      const json = (await resp.json()) as { field: IField }
      return json.field
    },
    { tableId, name, fieldType, owner, apiBase: API_BASE_STR },
  )
}

async function reloadGrid(page: Page): Promise<void> {
  await page.reload()
  await expect(page.locator('.bitable-grid-scope')).toBeVisible({ timeout: 15_000 })
}

test.describe('Bitable Record Detail Drawer E2E (U3)', () => {
  test.beforeAll(async () => {
    try {
      await waitForServer(undefined, 5_000)
    } catch {
      test.skip(true, 'Backend not running — skipping record drawer E2E')
    }
  })

  test('D1: clicking a row seq cell opens the drawer with all fields', async ({ page }) => {
    const { tableId, fields } = await loginAndCreateTable(page, 'E2E抽屉打开', '测试表')
    const titleField = fields.find((f) => f.field_type === 'text')!
    await createRecordViaApi(page, tableId, { [titleField.id]: '测试记录1' })
    await reloadGrid(page)

    // Click the seq cell (row number column) of the first data row.
    const seqCell = page.locator('.vxe-body--column.col--seq').first()
    await expect(seqCell).toBeVisible({ timeout: 10_000 })
    await seqCell.click()

    // Drawer opens
    await expect(page.locator('.record-detail-drawer')).toBeVisible({ timeout: 5_000 })
    // The sticky header shows the record title
    await expect(page.locator('.record-detail-drawer__header-title')).toContainText(
      '测试记录1',
      { timeout: 5_000 },
    )
    // Field labels are rendered (default table has 5 fields incl. attachment-free types)
    await expect(page.locator('.record-detail-drawer__field')).toHaveCount(5, {
      timeout: 5_000,
    })
  })

  test('D2: attachment/image fields render thumbnails, not raw URLs', async ({ page }) => {
    const { tableId, fields } = await loginAndCreateTable(page, 'E2E附件图片', '测试表')
    const titleField = fields.find((f) => f.field_type === 'text')!
    // Add attachment + image fields
    const attachmentField = await createFieldViaApi(page, tableId, '附件', 'attachment')
    const imageField = await createFieldViaApi(page, tableId, '图片', 'image')

    // Seed a record with attachment/image metadata
    await createRecordViaApi(page, tableId, {
      [titleField.id]: '带附件记录',
      [attachmentField.id]: [{ filename: 'doc.pdf', url: '/files/doc.pdf', size: 1024 }],
      [imageField.id]: [{ filename: 'pic.png', url: '/files/pic.png', size: 2048 }],
    })
    await reloadGrid(page)

    await page.locator('.vxe-body--column.col--seq').first().click()
    await expect(page.locator('.record-detail-drawer')).toBeVisible({ timeout: 5_000 })

    // Attachment cell renders a download link (not a raw URL string)
    await expect(page.locator('.record-detail-drawer .attachment-cell__link')).toHaveCount(
      1,
      { timeout: 5_000 },
    )
    // Image cell renders a thumbnail <img>
    await expect(page.locator('.record-detail-drawer .image-cell__thumb')).toHaveCount(1, {
      timeout: 5_000,
    })
  })

  test('D3: formula field renders read-only (no input control)', async ({ page }) => {
    const { tableId, fields } = await loginAndCreateTable(page, 'E2E公式只读', '测试表')
    const titleField = fields.find((f) => f.field_type === 'text')!
    const numberField = fields.find((f) => f.field_type === 'number')
    // Add a number field + a formula field referencing it
    const numField = numberField ?? (await createFieldViaApi(page, tableId, '数值', 'number'))
    const formulaField = await createFieldViaApi(
      page,
      tableId,
      '公式',
      'formula',
    )
    await createRecordViaApi(page, tableId, {
      [titleField.id]: '公式记录',
      [numField.id]: 42,
      // formula value is computed server-side; seed a cached value for display
      [formulaField.id]: 84,
    })
    await reloadGrid(page)

    await page.locator('.vxe-body--column.col--seq').first().click()
    await expect(page.locator('.record-detail-drawer')).toBeVisible({ timeout: 5_000 })

    // The formula field's value row must NOT contain an editable input.
    const formulaRow = page
      .locator('.record-detail-drawer__field')
      .filter({ hasText: '公式' })
    await expect(formulaRow).toBeVisible({ timeout: 5_000 })
    await expect(formulaRow.locator('input')).toHaveCount(0)
    // The computed value is shown as read-only text
    await expect(formulaRow.locator('.record-detail-drawer__readonly')).toContainText('84')
  })

  test('D4: editing a user-owned field preserves agent columns', async ({ page }) => {
    const { tableId, fields } = await loginAndCreateTable(page, 'E2E保留agent列', '测试表')
    const titleField = fields.find((f) => f.field_type === 'text')!
    const agentField = fields.find((f) => f.owner === 'agent')!
    await createRecordViaApi(page, tableId, {
      [titleField.id]: '编辑前标题',
      [agentField.id]: 'AGENT_VALUE',
    })
    await reloadGrid(page)

    await page.locator('.vxe-body--column.col--seq').first().click()
    await expect(page.locator('.record-detail-drawer')).toBeVisible({ timeout: 5_000 })

    // Edit the user-owned title field
    const titleInput = page.locator('.record-detail-drawer__field').first().locator('input')
    await titleInput.fill('编辑后标题')
    await page.locator('.record-detail-drawer__footer').getByRole('button', { name: '保存' }).click()

    // Drawer closes on success
    await expect(page.locator('.record-detail-drawer')).toHaveCount(0, { timeout: 10_000 })

    // Re-open the drawer — agent column must still show AGENT_VALUE
    await page.locator('.vxe-body--column.col--seq').first().click()
    await expect(page.locator('.record-detail-drawer')).toBeVisible({ timeout: 5_000 })
    const agentRow = page
      .locator('.record-detail-drawer__field')
      .filter({ hasText: agentField.name })
    await expect(agentRow).toContainText('AGENT_VALUE', { timeout: 5_000 })
  })

  test('D5: drawer width is 480px when field count <= 10', async ({ page }) => {
    const { tableId, fields } = await loginAndCreateTable(page, 'E2E宽度480', '测试表')
    const titleField = fields.find((f) => f.field_type === 'text')!
    await createRecordViaApi(page, tableId, { [titleField.id]: '宽度测试' })
    await reloadGrid(page)

    await page.locator('.vxe-body--column.col--seq').first().click()
    const drawer = page.locator('.record-detail-drawer')
    await expect(drawer).toBeVisible({ timeout: 5_000 })

    // Default table has 5 fields (<=10). Resolved width should be 480px.
    const width = await drawer.evaluate((el) => {
      // The a-drawer width is applied to the .ant-drawer-content wrapper.
      const content = el.querySelector('.ant-drawer-content') ?? el
      return getComputedStyle(content).width
    })
    expect(width).toBe('480px')
  })

  test('D6: drawer width is 640px when field count > 10', async ({ page }) => {
    const { tableId, fields } = await loginAndCreateTable(page, 'E2E宽度640', '测试表')
    const titleField = fields.find((f) => f.field_type === 'text')!
    // Default table has 5 fields; add 7 more to exceed 10.
    for (let i = 0; i < 7; i++) {
      await createFieldViaApi(page, tableId, `扩展字段${i}`, 'text')
    }
    await createRecordViaApi(page, tableId, { [titleField.id]: '宽屏测试' })
    await reloadGrid(page)

    await page.locator('.vxe-body--column.col--seq').first().click()
    const drawer = page.locator('.record-detail-drawer')
    await expect(drawer).toBeVisible({ timeout: 5_000 })

    const width = await drawer.evaluate((el) => {
      const content = el.querySelector('.ant-drawer-content') ?? el
      return getComputedStyle(content).width
    })
    expect(width).toBe('640px')
  })

  test('D7: Esc closes the drawer', async ({ page }) => {
    const { tableId, fields } = await loginAndCreateTable(page, 'E2E Esc关闭', '测试表')
    const titleField = fields.find((f) => f.field_type === 'text')!
    await createRecordViaApi(page, tableId, { [titleField.id]: 'Esc测试' })
    await reloadGrid(page)

    await page.locator('.vxe-body--column.col--seq').first().click()
    await expect(page.locator('.record-detail-drawer')).toBeVisible({ timeout: 5_000 })

    await page.keyboard.press('Escape')
    await expect(page.locator('.record-detail-drawer')).toHaveCount(0, { timeout: 5_000 })
  })
})
