/**
 * E2E tests for U6: R15a BitableTool agent parity — the 4 new actions
 * (create_view / update_view / update_field / delete_view) and the
 * DELETE /views/{id} endpoint.
 *
 * These tests verify the UI flows that correspond to the 4 new BitableTool
 * actions, focusing on the delete-view flow (the main U6 frontend feature)
 * and the 204/409 contract of the DELETE endpoint.
 *
 * Route mocking is used for the DELETE /views endpoint so the 204 success
 * and 409 last-view scenarios are deterministic and don't depend on
 * specific backend view persistence state.
 *
 * ponytail: scenarios reuse the loginAndOpenBitable + openFileDetailWithTable
 * helpers from bitable-view.spec.ts. Each test creates a unique file to
 * avoid collisions. The backend may not be fully configured — tests skip
 * gracefully if the server is down.
 */

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

async function loginAndOpenBitable(page: Page): Promise<void> {
  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 })
  await expect(page.locator('.bitable-file-list-view')).toBeVisible({ timeout: 15_000 })
}

/**
 * Log in, create a bitable file + table via the UI, and wait for the
 * ViewSwitcher to render. Returns once the grid header is visible.
 */
async function openFileDetailWithTable(page: Page, label: string): Promise<void> {
  await loginAndOpenBitable(page)
  await page.getByRole('button', { name: /新建文件/ }).click()
  await page.getByPlaceholder('请输入文件名').fill(`U6-${label}`)
  await page.getByRole('button', { name: /确\s*定/ }).click()
  await expect(page).toHaveURL(/\/bitable\/[^/]+/, { timeout: 10_000 })
  await page.locator('.table-view-list__header .ant-btn').click()
  await expect(page.getByText('新建数据表')).toBeVisible({ timeout: 5_000 })
  await page.getByPlaceholder('请输入表名').fill(`U6表-${label}`)
  await page.getByRole('button', { name: /确\s*定/ }).click()
  await expect(page.locator('.bitable-file-detail-view__table-name')).toContainText(
    `U6表-${label}`,
    { timeout: 10_000 },
  )
}

/**
 * Mock the list views GET to return the given view objects, so the
 * ViewSwitcher renders the exact set of views needed for each test.
 */
async function mockListViewsWith(
  page: Page,
  views: { id: string; name: string; view_type: string; config: Record<string, unknown> }[],
): Promise<void> {
  await page.route('**/api/v1/bitable/tables/*/views', (route) => {
    if (route.request().method() !== 'GET') return route.continue()
    return route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ success: true, views }),
    })
  })
}

test.describe('Bitable Agent Parity E2E (U6: R15a)', () => {
  test.beforeAll(async () => {
    try {
      await waitForServer(undefined, 5_000)
    } catch {
      test.skip(true, 'Backend not running — skipping agent parity E2E')
    }
  })

  // ── P1: delete button hidden when only 1 view ──────────────────────

  test('P1: delete button hidden when only one view exists', async ({ page }) => {
    await openFileDetailWithTable(page, 'P1-single-view')
    await mockListViewsWith(page, [
      { id: 'v1', name: '默认视图', view_type: 'grid', config: {} },
    ])
    // Reload the table to pick up the mocked views.
    await page.reload()
    await expect(page.locator('.bitable-file-detail-view__table-name')).toBeVisible({
      timeout: 10_000,
    })

    // The delete button must NOT be visible (views.length === 1).
    await expect(page.getByRole('button', { name: /删除/ })).not.toBeVisible({
      timeout: 5_000,
    })
  })

  // ── P2: delete button visible when 2+ views ────────────────────────

  test('P2: delete button visible when 2+ views exist', async ({ page }) => {
    await openFileDetailWithTable(page, 'P2-two-views')
    await mockListViewsWith(page, [
      { id: 'v1', name: '视图A', view_type: 'grid', config: {} },
      { id: 'v2', name: '视图B', view_type: 'grid', config: {} },
    ])
    await page.reload()
    await expect(page.locator('.bitable-file-detail-view__table-name')).toBeVisible({
      timeout: 10_000,
    })

    // The delete button must be visible.
    await expect(page.getByRole('button', { name: /删除/ })).toBeVisible({
      timeout: 5_000,
    })
  })

  // ── P3: popconfirm appears on delete click ─────────────────────────

  test('P3: clicking delete shows popconfirm with "确认删除此视图？"', async ({ page }) => {
    await openFileDetailWithTable(page, 'P3-popconfirm')
    await mockListViewsWith(page, [
      { id: 'v1', name: '视图A', view_type: 'grid', config: {} },
      { id: 'v2', name: '视图B', view_type: 'grid', config: {} },
    ])
    await page.reload()
    await expect(page.locator('.bitable-file-detail-view__table-name')).toBeVisible({
      timeout: 10_000,
    })

    await page.getByRole('button', { name: /删除/ }).click()
    // The popconfirm overlay should appear with the confirmation text.
    await expect(page.getByText('确认删除此视图？')).toBeVisible({ timeout: 5_000 })
    await expect(page.getByRole('button', { name: /删\s*除/ }).last()).toBeVisible()
    await expect(page.getByRole('button', { name: /取\s*消/ })).toBeVisible()
  })

  // ── P4: cancel popconfirm does not delete ──────────────────────────

  test('P4: cancel popconfirm does not fire DELETE request', async ({ page }) => {
    await openFileDetailWithTable(page, 'P4-cancel')
    await mockListViewsWith(page, [
      { id: 'v1', name: '视图A', view_type: 'grid', config: {} },
      { id: 'v2', name: '视图B', view_type: 'grid', config: {} },
    ])
    await page.reload()
    await expect(page.locator('.bitable-file-detail-view__table-name')).toBeVisible({
      timeout: 10_000,
    })

    let deleteFired = false
    await page.route('**/api/v1/bitable/views/*', (route) => {
      if (route.request().method() === 'DELETE') {
        deleteFired = true
        return route.fulfill({ status: 204 })
      }
      return route.continue()
    })

    await page.getByRole('button', { name: /删除/ }).click()
    await page.getByRole('button', { name: /取\s*消/ }).click()

    // Popconfirm disappears.
    await expect(page.getByText('确认删除此视图？')).not.toBeVisible({ timeout: 3_000 })
    // No DELETE request was fired.
    expect(deleteFired).toBe(false)
  })

  // ── P5: confirm delete fires DELETE /views/{id} ────────────────────

  test('P5: confirm delete fires DELETE /views/{id} and returns 204', async ({ page }) => {
    await openFileDetailWithTable(page, 'P5-delete-204')
    await mockListViewsWith(page, [
      { id: 'v1', name: '视图A', view_type: 'grid', config: {} },
      { id: 'v-del', name: '待删除', view_type: 'grid', config: {} },
    ])
    await page.reload()
    await expect(page.locator('.bitable-file-detail-view__table-name')).toBeVisible({
      timeout: 10_000,
    })

    let deletedViewId: string | null = null
    await page.route('**/api/v1/bitable/views/*', (route) => {
      if (route.request().method() === 'DELETE') {
        const url = route.request().url()
        deletedViewId = url.split('/views/')[1]?.split('?')[0] ?? null
        return route.fulfill({ status: 204 })
      }
      return route.continue()
    })

    await page.getByRole('button', { name: /删除/ }).click()
    await page.getByRole('button', { name: /删\s*除/ }).last().click()

    // The DELETE request was fired with the active view's id.
    await expect
      .poll(async () => deletedViewId, { timeout: 5_000 })
      .toBeTruthy()
  })

  // ── P6: 409 last view shows warning notification ───────────────────

  test('P6: 409 Conflict on last view shows warning notification', async ({ page }) => {
    await openFileDetailWithTable(page, 'P6-409-last-view')
    // Mock 2 views so the delete button is visible, but the backend
    // returns 409 (last view) — simulating a race where the other view
    // was deleted server-side between the list and the delete.
    await mockListViewsWith(page, [
      { id: 'v1', name: '视图A', view_type: 'grid', config: {} },
      { id: 'v2', name: '视图B', view_type: 'grid', config: {} },
    ])
    await page.reload()
    await expect(page.locator('.bitable-file-detail-view__table-name')).toBeVisible({
      timeout: 10_000,
    })

    await page.route('**/api/v1/bitable/views/*', (route) => {
      if (route.request().method() === 'DELETE') {
        return route.fulfill({
          status: 409,
          contentType: 'application/json',
          body: JSON.stringify({ detail: 'Cannot delete the last view of a table' }),
        })
      }
      return route.continue()
    })

    await page.getByRole('button', { name: /删除/ }).click()
    await page.getByRole('button', { name: /删\s*除/ }).last().click()

    // The warning notification should appear with the "无法删除" message.
    await expect(page.getByText('无法删除')).toBeVisible({ timeout: 5_000 })
  })

  // ── P7: successful delete removes the view from the tab list ───────

  test('P7: successful delete removes view from tab list (optimistic update)', async ({
    page,
  }) => {
    await openFileDetailWithTable(page, 'P7-removes-tab')
    await mockListViewsWith(page, [
      { id: 'v-keep', name: '保留视图', view_type: 'grid', config: {} },
      { id: 'v-del', name: '删除视图', view_type: 'grid', config: {} },
    ])
    await page.reload()
    await expect(page.locator('.bitable-file-detail-view__table-name')).toBeVisible({
      timeout: 10_000,
    })

    // Both view tabs should be visible initially.
    await expect(page.getByRole('tab', { name: '保留视图' })).toBeVisible({ timeout: 5_000 })
    await expect(page.getByRole('tab', { name: '删除视图' })).toBeVisible({ timeout: 5_000 })

    await page.route('**/api/v1/bitable/views/*', (route) => {
      if (route.request().method() === 'DELETE') {
        return route.fulfill({ status: 204 })
      }
      return route.continue()
    })

    // Switch to the view we want to delete, then delete it.
    await page.getByRole('tab', { name: '删除视图' }).click()
    await page.getByRole('button', { name: /删除/ }).click()
    await page.getByRole('button', { name: /删\s*除/ }).last().click()

    // The deleted view's tab should disappear.
    await expect(page.getByRole('tab', { name: '删除视图' })).not.toBeVisible({
      timeout: 5_000,
    })
    // The remaining view's tab should still be visible.
    await expect(page.getByRole('tab', { name: '保留视图' })).toBeVisible({
      timeout: 5_000,
    })
  })

  // ── P8: deleting active view switches to first remaining ───────────

  test('P8: deleting active view switches to the first remaining view', async ({ page }) => {
    await openFileDetailWithTable(page, 'P8-switch-after-delete')
    await mockListViewsWith(page, [
      { id: 'v-first', name: '第一个视图', view_type: 'grid', config: {} },
      { id: 'v-second', name: '第二个视图', view_type: 'grid', config: {} },
      { id: 'v-third', name: '第三个视图', view_type: 'grid', config: {} },
    ])
    await page.reload()
    await expect(page.locator('.bitable-file-detail-view__table-name')).toBeVisible({
      timeout: 10_000,
    })

    await page.route('**/api/v1/bitable/views/*', (route) => {
      if (route.request().method() === 'DELETE') {
        return route.fulfill({ status: 204 })
      }
      return route.continue()
    })

    // Switch to the second view, then delete it.
    await page.getByRole('tab', { name: '第二个视图' }).click()
    await page.getByRole('button', { name: /删除/ }).click()
    await page.getByRole('button', { name: /删\s*除/ }).last().click()

    // The first view should become the active tab.
    await expect(page.getByRole('tab', { name: '第一个视图' })).toHaveClass(
      /ant-tabs-tab-active/,
      { timeout: 5_000 },
    )
  })

  // ── P9: 404 on delete (non-owned view) shows error notification ────

  test('P9: 404 on delete shows error notification (non-owner / missing)', async ({ page }) => {
    await openFileDetailWithTable(page, 'P9-404-not-found')
    await mockListViewsWith(page, [
      { id: 'v1', name: '视图A', view_type: 'grid', config: {} },
      { id: 'v2', name: '视图B', view_type: 'grid', config: {} },
    ])
    await page.reload()
    await expect(page.locator('.bitable-file-detail-view__table-name')).toBeVisible({
      timeout: 10_000,
    })

    await page.route('**/api/v1/bitable/views/*', (route) => {
      if (route.request().method() === 'DELETE') {
        return route.fulfill({
          status: 404,
          contentType: 'application/json',
          body: JSON.stringify({ detail: 'View not found' }),
        })
      }
      return route.continue()
    })

    await page.getByRole('button', { name: /删除/ }).click()
    await page.getByRole('button', { name: /删\s*除/ }).last().click()

    // An error notification should appear.
    await expect(page.getByText('删除视图失败')).toBeVisible({ timeout: 5_000 })
  })

  // ── P10: create view adds new tab to the switcher ──────────────────

  test('P10: creating a new view adds it to the tab list', async ({ page }) => {
    await openFileDetailWithTable(page, 'P10-create-adds-tab')
    await mockListViewsWith(page, [
      { id: 'v1', name: '默认视图', view_type: 'grid', config: {} },
    ])
    await page.reload()
    await expect(page.locator('.bitable-file-detail-view__table-name')).toBeVisible({
      timeout: 10_000,
    })

    // Initially only 1 tab.
    await expect(page.getByRole('tab', { name: '默认视图' })).toBeVisible({ timeout: 5_000 })

    // Mock the POST /views response.
    await page.route('**/api/v1/bitable/tables/*/views', (route) => {
      if (route.request().method() === 'POST') {
        const body = route.request().postDataJSON() ?? {}
        return route.fulfill({
          status: 201,
          contentType: 'application/json',
          body: JSON.stringify({
            success: true,
            view: {
              id: 'v-new',
              table_id: 'tbl',
              name: body.name ?? '新视图',
              view_type: 'grid',
              config: {},
              created_at: new Date().toISOString(),
            },
          }),
        })
      }
      return route.continue()
    })

    // Open the "新建视图" dropdown and click "表格" (grid).
    await page.getByRole('button', { name: /新建视图/ }).click()
    await page.locator('.ant-dropdown-menu-item').filter({ hasText: '表格' }).click()

    // Fill the name modal.
    const nameInput = page.getByPlaceholder('请输入视图名称')
    await expect(nameInput).toBeVisible({ timeout: 5_000 })
    await nameInput.fill('新创建视图')
    await page.getByRole('button', { name: /确\s*定/ }).click()

    // The new view tab should appear.
    await expect(page.getByRole('tab', { name: '新创建视图' })).toBeVisible({
      timeout: 5_000,
    })
  })
})
