/**
 * E2E tests for Bitable grouping + conditional formatting (U5 / R4).
 *
 * Flow: login → create file → create table → seed records via API →
 *       PATCH view config with group_by / conditional_formatting →
 *       reload grid → assert group headers + CF row coloring.
 *
 * ponytail: setup goes through the REST API (page.evaluate fetch) rather
 * than UI clicks — setup is not the thing under test. Ceiling: API path
 * couples the test to the /views PATCH contract; if that contract shifts,
 * setup breaks (acceptable — the same contract is exercised by the UI).
 *
 * 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>
}

interface IView {
  id: string
  name: string
  config: Record<string, unknown>
}

const API_BASE_STR = API_BASE

async function loginAndCreateTable(
  page: Page,
  fileName: string,
  tableName: string,
): Promise<{ tableId: string; fields: IField[]; viewId: string }> {
  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 + default view via API
  const { tableId, fields, viewId } = 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 }[] }
    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[] }
    const viewsResp = await fetch(`${apiBase}/bitable/tables/${table.id}/views`, {
      headers: { Authorization: `Bearer ${token}` },
    })
    const viewsJson = (await viewsResp.json()) as { views: IView[] }
    return {
      tableId: table.id,
      fields: fieldsJson.fields,
      viewId: viewsJson.views[0]?.id ?? '',
    }
  }, API_BASE_STR)
  return { tableId, fields, viewId }
}

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 updateViewConfig(
  page: Page,
  viewId: string,
  config: Record<string, unknown>,
): Promise<void> {
  await page.evaluate(
    async ({ viewId, config, apiBase }) => {
      const token = localStorage.getItem('agentkit.access_token') ?? ''
      await fetch(`${apiBase}/bitable/views/${viewId}`, {
        method: 'PATCH',
        headers: {
          Authorization: `Bearer ${token}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ config }),
      })
    },
    { viewId, config, 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 Grouping + Conditional Formatting E2E (U5)', () => {
  test.beforeAll(async () => {
    try {
      await waitForServer(undefined, 5_000)
    } catch {
      test.skip(true, 'Backend not running — skipping grouping E2E')
    }
  })

  test('G1: group by one field renders group headers with value + count', async ({ page }) => {
    const { tableId, fields, viewId } = await loginAndCreateTable(page, 'E2E分组G1', '测试表')
    const titleField = fields.find((f) => f.field_type === 'text')!
    const statusField = await createFieldViaApi(page, tableId, '状态', 'select')

    await createRecordViaApi(page, tableId, { [titleField.id]: '记录A', [statusField.id]: '进行中' })
    await createRecordViaApi(page, tableId, { [titleField.id]: '记录B', [statusField.id]: '已完成' })
    await createRecordViaApi(page, tableId, { [titleField.id]: '记录C', [statusField.id]: '进行中' })

    await updateViewConfig(page, viewId, {
      group_by: [{ field_id: statusField.id, direction: 'asc' }],
    })
    await reloadGrid(page)

    // Group headers appear
    const headers = page.locator('.bitable-grid-scope__group-header')
    await expect(headers).toHaveCount(2, { timeout: 10_000 })

    // The "进行中" group has 2 records
    const headerInProgress = headers.filter({ hasText: '进行中' })
    await expect(headerInProgress).toBeVisible()
    await expect(headerInProgress).toContainText('2 条')

    // The "已完成" group has 1 record
    const headerDone = headers.filter({ hasText: '已完成' })
    await expect(headerDone).toBeVisible()
    await expect(headerDone).toContainText('1 条')
  })

  test('G2: clicking a group header collapses its records', async ({ page }) => {
    const { tableId, fields, viewId } = await loginAndCreateTable(page, 'E2E分组G2', '测试表')
    const titleField = fields.find((f) => f.field_type === 'text')!
    const statusField = await createFieldViaApi(page, tableId, '状态', 'select')

    await createRecordViaApi(page, tableId, { [titleField.id]: '记录A', [statusField.id]: '进行中' })
    await createRecordViaApi(page, tableId, { [titleField.id]: '记录B', [statusField.id]: '已完成' })

    await updateViewConfig(page, viewId, {
      group_by: [{ field_id: statusField.id, direction: 'asc' }],
    })
    await reloadGrid(page)

    const headerInProgress = page.locator('.bitable-grid-scope__group-header').first()
    await expect(headerInProgress).toBeVisible({ timeout: 10_000 })

    // Count vxe-table body rows before collapse (should have data rows)
    const rowsBefore = await page.locator('.vxe-body--row').count()
    expect(rowsBefore).toBeGreaterThan(0)

    // Click to collapse
    await headerInProgress.click()

    // After collapse, the caret shows ▸ (collapsed indicator)
    await expect(headerInProgress.locator('.bitable-grid-scope__caret')).toContainText('▸')
  })

  test('G3: clicking a collapsed group header expands its records', async ({ page }) => {
    const { tableId, fields, viewId } = await loginAndCreateTable(page, 'E2E分组G3', '测试表')
    const titleField = fields.find((f) => f.field_type === 'text')!
    const statusField = await createFieldViaApi(page, tableId, '状态', 'select')

    await createRecordViaApi(page, tableId, { [titleField.id]: '记录A', [statusField.id]: '进行中' })
    await createRecordViaApi(page, tableId, { [titleField.id]: '记录B', [statusField.id]: '已完成' })

    await updateViewConfig(page, viewId, {
      group_by: [{ field_id: statusField.id, direction: 'asc' }],
    })
    await reloadGrid(page)

    const header = page.locator('.bitable-grid-scope__group-header').first()
    await expect(header).toBeVisible({ timeout: 10_000 })

    // Collapse
    await header.click()
    await expect(header.locator('.bitable-grid-scope__caret')).toContainText('▸')

    // Expand
    await header.click()
    await expect(header.locator('.bitable-grid-scope__caret')).toContainText('▾')
  })

  test('G4: multi-level grouping (2 fields) renders nested headers', async ({ page }) => {
    const { tableId, fields, viewId } = await loginAndCreateTable(page, 'E2E分组G4', '测试表')
    const titleField = fields.find((f) => f.field_type === 'text')!
    const statusField = await createFieldViaApi(page, tableId, '状态', 'select')
    const priorityField = await createFieldViaApi(page, tableId, '优先级', 'select')

    await createRecordViaApi(page, tableId, {
      [titleField.id]: '记录A',
      [statusField.id]: '进行中',
      [priorityField.id]: '高',
    })
    await createRecordViaApi(page, tableId, {
      [titleField.id]: '记录B',
      [statusField.id]: '进行中',
      [priorityField.id]: '低',
    })
    await createRecordViaApi(page, tableId, {
      [titleField.id]: '记录C',
      [statusField.id]: '已完成',
      [priorityField.id]: '高',
    })

    await updateViewConfig(page, viewId, {
      group_by: [
        { field_id: statusField.id, direction: 'asc' },
        { field_id: priorityField.id, direction: 'asc' },
      ],
    })
    await reloadGrid(page)

    // 2 top-level groups (进行中, 已完成) + 3 sub-groups (高, 低, 高) = 5 headers
    const headers = page.locator('.bitable-grid-scope__group-header')
    await expect(headers).toHaveCount(5, { timeout: 10_000 })

    // Sub-group headers have more padding (depth=1)
    const subHeaders = headers.filter({ hasText: '高' })
    await expect(subHeaders.first()).toBeVisible()
  })

  test('G5: conditional format "equals" colors matching rows', async ({ page }) => {
    const { tableId, fields, viewId } = await loginAndCreateTable(page, 'E2E条件格式G5', '测试表')
    const titleField = fields.find((f) => f.field_type === 'text')!
    const statusField = await createFieldViaApi(page, tableId, '状态', 'select')

    await createRecordViaApi(page, tableId, { [titleField.id]: '记录A', [statusField.id]: '已完成' })
    await createRecordViaApi(page, tableId, { [titleField.id]: '记录B', [statusField.id]: '进行中' })

    await updateViewConfig(page, viewId, {
      conditional_formatting: [
        {
          field_id: statusField.id,
          operator: 'equals',
          value: '已完成',
          color_key: 'green',
          bold: true,
          enabled: true,
        },
      ],
    })
    await reloadGrid(page)

    // At least one row should have the green CF class
    const greenRows = page.locator('.bitable-cf--green')
    await expect(greenRows.first()).toBeVisible({ timeout: 10_000 })

    // The green row should also be bold
    const greenBoldRows = page.locator('.bitable-cf--green.bitable-cf--bold')
    await expect(greenBoldRows.first()).toBeVisible()
  })

  test('G6: conditional format "between" colors rows in numeric range', async ({ page }) => {
    const { tableId, fields, viewId } = await loginAndCreateTable(page, 'E2E条件格式G6', '测试表')
    const titleField = fields.find((f) => f.field_type === 'text')!
    const numberField = await createFieldViaApi(page, tableId, '数值', 'number')

    await createRecordViaApi(page, tableId, { [titleField.id]: '记录A', [numberField.id]: 50 })
    await createRecordViaApi(page, tableId, { [titleField.id]: '记录B', [numberField.id]: 150 })
    await createRecordViaApi(page, tableId, { [titleField.id]: '记录C', [numberField.id]: 250 })

    await updateViewConfig(page, viewId, {
      conditional_formatting: [
        {
          field_id: numberField.id,
          operator: 'between',
          value: '100,200',
          color_key: 'yellow',
          bold: false,
          enabled: true,
        },
      ],
    })
    await reloadGrid(page)

    // Only the row with value 150 should be colored yellow
    const yellowRows = page.locator('.bitable-cf--yellow')
    await expect(yellowRows).toHaveCount(1, { timeout: 10_000 })
  })

  test('G7: group + CF combined — CF only on data cells, not group headers', async ({ page }) => {
    const { tableId, fields, viewId } = await loginAndCreateTable(page, 'E2E组合G7', '测试表')
    const titleField = fields.find((f) => f.field_type === 'text')!
    const statusField = await createFieldViaApi(page, tableId, '状态', 'select')

    await createRecordViaApi(page, tableId, { [titleField.id]: '记录A', [statusField.id]: '已完成' })
    await createRecordViaApi(page, tableId, { [titleField.id]: '记录B', [statusField.id]: '进行中' })
    await createRecordViaApi(page, tableId, { [titleField.id]: '记录C', [statusField.id]: '已完成' })

    await updateViewConfig(page, viewId, {
      group_by: [{ field_id: statusField.id, direction: 'asc' }],
      conditional_formatting: [
        {
          field_id: statusField.id,
          operator: 'equals',
          value: '已完成',
          color_key: 'green',
          bold: true,
          enabled: true,
        },
      ],
    })
    await reloadGrid(page)

    // Group headers exist
    const headers = page.locator('.bitable-grid-scope__group-header')
    await expect(headers).toHaveCount(2, { timeout: 10_000 })

    // Group headers must NOT have CF classes
    const coloredHeaders = headers.filter({
      has: page.locator('.bitable-cf--green'),
    })
    await expect(coloredHeaders).toHaveCount(0)

    // Data rows DO have CF classes (the "已完成" group's rows are green)
    const greenDataRows = page.locator('.vxe-body--row.bitable-cf--green')
    await expect(greenDataRows.first()).toBeVisible({ timeout: 10_000 })
  })

  test('G8: group header shows SUM + AVG aggregation for number fields', async ({ page }) => {
    const { tableId, fields, viewId } = await loginAndCreateTable(page, 'E2E聚合G8', '测试表')
    const titleField = fields.find((f) => f.field_type === 'text')!
    const statusField = await createFieldViaApi(page, tableId, '状态', 'select')
    const amountField = await createFieldViaApi(page, tableId, '金额', 'number')

    await createRecordViaApi(page, tableId, {
      [titleField.id]: '记录A',
      [statusField.id]: '进行中',
      [amountField.id]: 100,
    })
    await createRecordViaApi(page, tableId, {
      [titleField.id]: '记录B',
      [statusField.id]: '进行中',
      [amountField.id]: 200,
    })
    await createRecordViaApi(page, tableId, {
      [titleField.id]: '记录C',
      [statusField.id]: '已完成',
      [amountField.id]: 50,
    })

    await updateViewConfig(page, viewId, {
      group_by: [{ field_id: statusField.id, direction: 'asc' }],
    })
    await reloadGrid(page)

    // The "进行中" group header should show aggregation: 合计 300 · 均值 150
    const headerInProgress = page
      .locator('.bitable-grid-scope__group-header')
      .filter({ hasText: '进行中' })
    await expect(headerInProgress).toBeVisible({ timeout: 10_000 })
    await expect(headerInProgress).toContainText('合计')
    await expect(headerInProgress).toContainText('300')
    await expect(headerInProgress).toContainText('均值')
    await expect(headerInProgress).toContainText('150')
  })
})
