import { expect, test } from '@playwright/test'
import { execFileSync } from 'node:child_process'

const apiBase = (process.env.API_BASE || 'http://localhost:4000/api').replace(/\/+$/, '')
const adminToken = process.env.ADMIN_TOKEN || 'dev-admin-token'
const runId = process.env.RUN_ID || `REGRESSION-ROLE-${new Date().toISOString().replace(/[-:.TZ]/g, '').slice(0, 14)}-${process.pid}`
const runIpSegment = (Date.now() % 200) + 20

function uniquePhone(offset = 0) {
  const seed = Math.floor(Math.random() * 100000000) + offset
  return `137${String(seed).padStart(8, '0')}`
}

function testClientIp(offset = 0) {
  return `10.71.${runIpSegment}.${(offset % 200) + 20}`
}

async function api(path: string, options: RequestInit = {}, clientIp = testClientIp()) {
  const response = await fetch(`${apiBase}${path}`, {
    ...options,
    headers: {
      'content-type': 'application/json',
      'x-forwarded-for': clientIp,
      ...(options.headers || {}),
    },
  })
  const text = await response.text()
  const data = text ? JSON.parse(text) : null
  if (!response.ok) {
    const error = new Error(data?.message || `${path} failed with ${response.status}`) as Error & { status?: number }
    error.status = response.status
    throw error
  }
  return data
}

async function apiStatus(path: string, token: string, clientIp = testClientIp(99)) {
  const response = await fetch(`${apiBase}${path}`, {
    headers: { 'x-admin-token': token, 'x-forwarded-for': clientIp },
  })
  return response.status
}

function latestLocalPhoneCode(phone: string) {
  const database = process.env.PGDATABASE || process.env.DATABASE_NAME || 'dlbdi_dev'
  try {
    return execFileSync('psql', [
      '-At',
      '-d',
      database,
      '-c',
      `select code from phone_codes where phone = '${phone}' and expires_at > now() order by created_at desc limit 1;`,
    ], {
      encoding: 'utf8',
      stdio: ['ignore', 'pipe', 'ignore'],
    }).trim()
  } catch {
    return ''
  }
}

async function createStaffToken(role: string, offset: number) {
  const phone = uniquePhone(offset)
  const clientIp = testClientIp(offset)
  await api('/admin/staff-accounts', {
    method: 'POST',
    headers: { 'x-admin-token': adminToken },
    body: JSON.stringify({ phone, displayName: `${runId}-${role}`, role }),
  })
  const sent = await api('/auth/phone/send-code', {
    method: 'POST',
    body: JSON.stringify({ phone }),
  }, clientIp)
  const login = await api('/staff-auth/phone-login', {
    method: 'POST',
    body: JSON.stringify({ phone, code: sent.demoCode || latestLocalPhoneCode(phone) || process.env.DEV_AUTH_CODE || '123456' }),
  }, clientIp)
  return login.token as string
}

async function openAdminAs(page: import('@playwright/test').Page, token: string, path = '/admin') {
  await page.addInitScript((value) => {
    window.localStorage.setItem('dlbdi:staffToken', value)
  }, token)
  await page.goto(path, { waitUntil: 'domcontentloaded' })
}

test('finance role can see finance but cannot edit products or roles', async ({ page }) => {
  const token = await createStaffToken('finance_admin', 1)
  expect(await apiStatus('/admin/revenue', token)).toBe(200)
  expect(await apiStatus('/admin/staff-roles', token)).toBe(403)

  await openAdminAs(page, token, '/admin/revenue')
  await expect(page.getByRole('button', { name: '财务中心' })).toBeVisible()
  await expect(page.getByRole('button', { name: '组织权限' })).toHaveCount(0)
})

test('checkin role only sees on-site checkin surface', async ({ page }) => {
  const token = await createStaffToken('checkin_staff', 2)
  expect(await apiStatus('/staff/event-registrations/not-found', token)).toBe(404)
  expect(await apiStatus('/admin/revenue', token)).toBe(403)

  await openAdminAs(page, token, '/admin/events/checkin')
  await expect(page.getByRole('heading', { name: '现场核销工作台' })).toBeVisible()
  await expect(page.getByRole('button', { name: '现场核销' })).toBeVisible()
  await expect(page.getByRole('button', { name: '财务中心' })).toHaveCount(0)
  await expect(page.getByRole('button', { name: '用户与会员' })).toHaveCount(0)
})

test('data service staff can process data requests but not finance or content config', async ({ page }) => {
  const token = await createStaffToken('data_service_staff', 3)
  expect(await apiStatus('/admin/data-service-requests', token)).toBe(200)
  expect(await apiStatus('/admin/payments', token)).toBe(403)
  expect(await apiStatus('/admin/site/home', token)).toBe(403)

  await openAdminAs(page, token, '/admin/data-service/requests')
  await expect(page.getByRole('heading', { name: '数据服务需求' })).toBeVisible()
  await expect(page.getByRole('button', { name: '产品与服务' }).first()).toBeVisible()
  await expect(page.getByRole('button', { name: '财务中心' })).toHaveCount(0)
  await expect(page.getByRole('button', { name: '组织权限' })).toHaveCount(0)
})

test('invoice read-only permission hides write actions on invoice detail', async ({ page }) => {
  await page.route('**/api/staff-auth/me', async (route) => {
    await route.fulfill({
      contentType: 'application/json',
      body: JSON.stringify({
        id: 'readonly-finance',
        email: 'readonly@example.com',
        phone: '13000000000',
        displayName: '只读财务',
        role: 'finance_viewer',
        status: 'active',
        defaultEntry: '/admin/invoices',
        permissions: ['invoice:read'],
      }),
    })
  })
  await page.route('**/api/admin/invoice-applications/INV-READONLY', async (route) => {
    await route.fulfill({
      contentType: 'application/json',
      body: JSON.stringify({
        invoiceNo: 'INV-READONLY',
        orderNo: 'ORD-READONLY',
        invoiceType: 'normal_e_invoice',
        invoiceTypeLabel: '普通电子发票',
        title: '只读测试公司',
        taxNumber: '91300000000000000X',
        email: 'invoice@example.com',
        amount: 120000,
        status: 'processing',
        remark: '测试备注',
        userPhone: '13800000000',
        createdAt: new Date().toISOString(),
      }),
    })
  })

  await openAdminAs(page, 'readonly-token', '/admin/invoices/INV-READONLY')
  await expect(page.getByRole('heading', { name: '发票详情' })).toBeVisible()
  await expect(page.getByText('当前账号仅可查看发票申请')).toBeVisible()
  await expect(page.getByRole('button', { name: '录入已开票' })).toHaveCount(0)
  await expect(page.getByRole('button', { name: '驳回' })).toHaveCount(0)
  await expect(page.getByRole('button', { name: '上传文件' })).toHaveCount(0)
})

test('content role can manage content but cannot enter finance or user records', async ({ page }) => {
  const token = await createStaffToken('content_admin', 4)
  expect(await apiStatus('/admin/site/home', token)).toBe(200)
  expect(await apiStatus('/admin/payments', token)).toBe(403)
  expect(await apiStatus('/admin/users', token)).toBe(403)

  await openAdminAs(page, token, '/admin/site/recommendations')
  await expect(page.getByRole('heading', { name: '推荐位配置' })).toBeVisible()
  await expect(page.getByRole('button', { name: '内容与营销' })).toBeVisible()
  await expect(page.getByRole('button', { name: '财务中心' })).toHaveCount(0)
  await expect(page.getByRole('button', { name: '用户与会员' })).toHaveCount(0)
})
