import { execFileSync } from 'node:child_process'

const apiBase = (process.env.API_BASE || process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:4000/api').replace(/\/+$/, '')
const localDevAdminToken = ['dev', 'admin', 'token'].join('-')
const adminToken = process.env.ADMIN_TOKEN || (process.env.NODE_ENV === 'production' ? '' : localDevAdminToken)
const runId = process.env.RUN_ID || `REGRESSION-${new Date().toISOString().replace(/[-:.TZ]/g, '').slice(0, 14)}-${process.pid}`
const runIpSuffix = Math.max(1, Date.now() % 240)
const runIp = process.env.TEST_CLIENT_IP || `127.42.${runIpSuffix}.${Math.max(1, process.pid % 240)}`

function assert(condition, message) {
  if (!condition) throw new Error(message)
}

async function request(path, options = {}) {
  const response = await fetch(`${apiBase}${path}`, {
    ...options,
    headers: {
      'content-type': 'application/json',
      'x-forwarded-for': runIp,
      ...(options.headers || {}),
    },
  })
  const text = await response.text()
  const data = text ? JSON.parse(text) : null
  if (!response.ok) {
    const error = new Error(data?.message || `${options.method || 'GET'} ${path} failed with ${response.status}`)
    error.status = response.status
    error.code = data?.code || data?.error
    error.data = data
    throw error
  }
  return data
}

async function requestRaw(path, options = {}) {
  const response = await fetch(`${apiBase}${path}`, {
    ...options,
    headers: {
      'x-forwarded-for': runIp,
      ...(options.headers || {}),
    },
  })
  const text = await response.text()
  const data = text ? JSON.parse(text) : null
  if (!response.ok) {
    const error = new Error(data?.message || `${options.method || 'GET'} ${path} failed with ${response.status}`)
    error.status = response.status
    error.code = data?.code || data?.error
    error.data = data
    throw error
  }
  return data
}

function adminHeaders(token = adminToken) {
  return token ? { 'x-admin-token': token } : {}
}

function bearer(token) {
  return { authorization: `Bearer ${token}` }
}

function uniquePhone(offset = 0) {
  const seed = (Date.now() + offset) % 100000000
  return `139${String(seed).padStart(8, '0')}`
}

function latestLocalPhoneCode(phone) {
  if (process.env.NODE_ENV === 'production') return ''
  if (!/^1\d{10}$/.test(phone)) return ''
  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 ''
  }
}

function sqlLiteral(value) {
  return `'${String(value ?? '').replace(/'/g, "''")}'`
}

function runLocalSql(sql) {
  const database = process.env.PGDATABASE || process.env.DATABASE_NAME || 'dlbdi_dev'
  execFileSync('psql', [
    '-v',
    'ON_ERROR_STOP=1',
    '-d',
    database,
    '-c',
    sql,
  ], {
    encoding: 'utf8',
    stdio: ['ignore', 'pipe', 'pipe'],
  })
}

function grantTestMembership(userId, level) {
  if (!['junior', 'senior'].includes(level)) return
  const productId = level === 'senior' ? 'membership-senior' : 'membership-junior'
  runLocalSql(`
    insert into membership_subscriptions (
      user_id, tier, product_id, billing_cycle, status, starts_at, expires_at, auto_renew
    )
    values (
      ${sqlLiteral(userId)}::uuid,
      ${sqlLiteral(level)},
      ${sqlLiteral(productId)},
      'manual_grant',
      'active',
      now() - interval '1 minute',
      now() + interval '30 days',
      false
    );

    insert into membership_events (
      user_id, event_type, from_tier, to_tier, from_status, to_status, payload_json
    )
    values (
      ${sqlLiteral(userId)}::uuid,
      'test_manual_grant',
      'registered',
      ${sqlLiteral(level)},
      'registered',
      'active',
      jsonb_build_object('source', 'business-closure-regression')
    );
  `)
}

function codeFromSendResult(phone, sent) {
  return sent.demoCode || latestLocalPhoneCode(phone) || process.env.DEV_AUTH_CODE || '123456'
}

async function staffPhoneLogin(phone) {
  const sent = await request('/auth/phone/send-code', {
    method: 'POST',
    body: JSON.stringify({ phone }),
  })
  const login = await request('/staff-auth/phone-login', {
    method: 'POST',
    body: JSON.stringify({ phone, code: codeFromSendResult(phone, sent) }),
  })
  assert(login.token, `未能登录员工账号 ${phone}`)
  return { ...login, phone }
}

async function createStaffAccount(role, displayName, offset) {
  const phone = uniquePhone(500 + offset)
  await request('/admin/staff-accounts', {
    method: 'POST',
    headers: adminHeaders(),
    body: JSON.stringify({ phone, displayName: `${runId}-${displayName}`, role }),
  })
  return staffPhoneLogin(phone)
}

async function expectForbidden(label, action) {
  try {
    await action()
  } catch (error) {
    assert(error.status === 403 || error.code === 'permission_denied', `${label} 应返回 403 权限拒绝`)
    return
  }
  throw new Error(`${label} 应被权限拒绝`)
}

async function createUser(level, offset) {
  const phone = uniquePhone(offset)
  const created = await request('/admin/users', {
    method: 'POST',
    headers: adminHeaders(),
    body: JSON.stringify({
      phone,
      displayName: `${runId}-回归测试${level}`,
      membershipLevel: level,
      onboardingCompleted: true,
      status: 'active',
    }),
  })
  grantTestMembership(created.id, level)
  const sent = await request('/auth/phone/send-code', {
    method: 'POST',
    body: JSON.stringify({ phone }),
  })
  const login = await request('/auth/phone/login', {
    method: 'POST',
    body: JSON.stringify({ phone, code: codeFromSendResult(phone, sent) }),
  })
  assert(login.token, `未能登录测试用户 ${phone}`)
  return login
}

async function assertRolePermissions() {
  const { items } = await request('/admin/staff-roles', { headers: adminHeaders() })
  const byCode = new Map(items.map((item) => [item.code, item.permissions || []]))
  const finance = byCode.get('finance_admin') || []
  const operations = byCode.get('operations_admin') || []
  const content = byCode.get('content_admin') || []
  const checkin = byCode.get('checkin_staff') || []
  const dataService = byCode.get('data_service_staff') || []

  for (const permission of ['payment:dev', 'product:write', 'staff:write', 'role:write']) {
    assert(!finance.includes(permission), `财务管理员不应拥有 ${permission}`)
  }
  for (const permission of ['event:checkin', 'task:update', 'data_service:update', 'site:write']) {
    assert(!operations.includes(permission), `运营管理员不应拥有 ${permission}`)
  }
  for (const permission of ['data_service:read', 'data_page:read', 'data_page:write']) {
    assert(operations.includes(permission), `运营管理员应拥有 ${permission}`)
  }
  for (const permission of ['cooperation:update', 'user:read', 'task:update', 'expert_application:read', 'expert_application:review', 'expert_booking:read', 'expert_schedule:read']) {
    assert(!content.includes(permission), `内容管理员不应拥有 ${permission}`)
  }
  for (const permission of ['expert_profile:read', 'expert_profile:write']) {
    assert(content.includes(permission), `内容管理员应拥有 ${permission}`)
  }
  for (const permission of ['data_page:read', 'data_page:write']) {
    assert(content.includes(permission), `内容管理员应拥有 ${permission}`)
  }
  for (const permission of ['data_service:read', 'data_service:update', 'user:read', 'data_page:read']) {
    assert(dataService.includes(permission), `数据服务专员应拥有 ${permission}`)
  }
  for (const permission of ['expert_profile:read', 'expert_profile:write', 'expert_application:read', 'expert_application:review', 'expert_booking:read', 'expert_booking:update', 'expert_schedule:read', 'expert_schedule:write']) {
    assert(operations.includes(permission), `运营管理员应拥有 ${permission}`)
  }
  assert(JSON.stringify(checkin.slice().sort()) === JSON.stringify(['event:checkin', 'event:read'].sort()), '活动核销员权限应只包含报名查看和核销')
}

async function assertPermissionDenials() {
  const [finance, operations, content, checkin] = await Promise.all([
    createStaffAccount('finance_admin', '回归测试财务', 1),
    createStaffAccount('operations_admin', '回归测试运营', 2),
    createStaffAccount('content_admin', '回归测试内容', 3),
    createStaffAccount('checkin_staff', '回归测试核销', 4),
  ])

  await expectForbidden('财务管理员不能创建商品', () => request('/admin/agent-products', {
    method: 'POST',
    headers: adminHeaders(finance.token),
    body: JSON.stringify({ title: '财务越权测试' }),
  }))
  await expectForbidden('运营管理员不能导出对账', () => request('/admin/reconcile', { headers: adminHeaders(operations.token) }))
  await expectForbidden('运营管理员不能运行会员提醒任务', () => request('/admin/memberships/jobs/expiry-reminders', {
    method: 'POST',
    headers: adminHeaders(operations.token),
  }))
  await expectForbidden('内容管理员不能查看专家申请', () => request('/admin/expert-applications', { headers: adminHeaders(content.token) }))
  await expectForbidden('内容管理员不能查看专家预约', () => request('/admin/expert-bookings', { headers: adminHeaders(content.token) }))
  await expectForbidden('内容管理员不能通过编号搜索专家申请', () => request('/admin/business-search?q=EXA20260601000001', { headers: adminHeaders(content.token) }))
  await expectForbidden('核销员不能查看财务收入', () => request('/admin/revenue', { headers: adminHeaders(checkin.token) }))
  await expectForbidden('核销员不能编辑商品', () => request('/admin/event-products', {
    method: 'POST',
    headers: adminHeaders(checkin.token),
    body: JSON.stringify({ title: '核销越权测试' }),
  }))
}

const levelConfig = {
  registered: { accessKey: 'registeredAccess', priceKey: 'registeredPriceAmount' },
  junior: { accessKey: 'juniorAccess', priceKey: 'juniorPriceAmount' },
  senior: { accessKey: 'seniorAccess', priceKey: 'seniorPriceAmount' },
}

function productId(product) {
  return product.productId || product.id
}

function isMatrixProduct(product) {
  return ['registeredAccess', 'juniorAccess', 'seniorAccess'].every((key) => typeof product[key] === 'string')
}

async function previewProduct(token, product) {
  return request('/checkout/preview', {
    method: 'POST',
    headers: bearer(token),
    body: JSON.stringify({
      items: [{ productId: productId(product), quantity: 1 }],
    }),
  })
}

async function assertMembershipMatrix() {
  const [agents, courses, events] = await Promise.all([
    request('/agents/catalog'),
    request('/courses/catalog'),
    request('/events/catalog'),
  ])
  const groups = {
    agent: (agents.items || []).filter(isMatrixProduct),
    course: (courses.items || []).filter(isMatrixProduct),
    event: (events.items || []).filter(isMatrixProduct),
  }
  for (const [kind, items] of Object.entries(groups)) {
    assert(items.length > 0, `没有找到可测试的 ${kind} 会员价矩阵商品`)
  }
  const products = Object.entries(groups).flatMap(([kind, items]) => items.map((item) => ({ kind, item })))
  assert(products.length > 0, '没有找到可测试的会员价矩阵商品')

  const users = {
    registered: await createUser('registered', 1),
    junior: await createUser('junior', 2),
    senior: await createUser('senior', 3),
  }
  const coverage = new Set()
  let tested = 0

  for (const { kind, item } of products) {
    for (const [level, config] of Object.entries(levelConfig)) {
      const access = item[config.accessKey]
      const expectedPrice = Math.max(0, Number(item[config.priceKey] ?? item.priceAmount ?? 0))
      try {
        const preview = await previewProduct(users[level].token, item)
        assert(access !== 'closed', `${kind} ${item.title} 对 ${level} 应禁止购买`)
        assert(preview.items?.length === 1, `${kind} ${item.title} 预览明细缺失`)
        const unitPrice = Number(preview.items[0].unitPriceAmount)
        if (access === 'free') assert(unitPrice === 0 && preview.totalAmount === 0, `${kind} ${item.title} 对 ${level} 应为免费`)
        if (access === 'paid') assert(unitPrice === expectedPrice, `${kind} ${item.title} 对 ${level} 会员价不正确`)
        coverage.add(`${kind}:${access}`)
        tested += 1
      } catch (error) {
        if (['event_full', 'course_full'].includes(error.code)) continue
        if (access === 'closed') {
          assert(error.status === 403, `${kind} ${item.title} 对 ${level} 禁购应返回 403`)
          coverage.add(`${kind}:closed`)
          tested += 1
          continue
        }
        throw error
      }
    }
  }

  assert(tested >= 3, '会员权益回归测试覆盖不足')
  for (const kind of Object.keys(groups)) {
    assert(Array.from(coverage).some((item) => item.startsWith(`${kind}:`)), `${kind} 会员权益没有完成有效覆盖`)
  }
  console.log(`会员矩阵覆盖：${Array.from(coverage).sort().join(', ')}`)
}

async function assertMembershipPaymentIdempotent() {
  const { token } = await createUser('registered', 10)
  const catalog = await request('/membership/catalog')
  const plan = (catalog.plans || []).find((item) => item.level === 'junior') || catalog.plans?.[0]
  assert(plan?.productId, '缺少可购买会员方案')

  const order = await request('/orders', {
    method: 'POST',
    headers: bearer(token),
    body: JSON.stringify({
      items: [{ productId: plan.productId, quantity: 1, billingCycle: 'monthly' }],
    }),
  })
  assert(order.items?.[0]?.productId === plan.productId, '会员下单必须保留目录返回的真实 productId')

  if (order.status === 'pending_payment') {
    const { paymentOrder } = await request('/payment-orders', {
      method: 'POST',
      headers: bearer(token),
      body: JSON.stringify({ orderNo: order.orderNo, channel: 'dev' }),
    })
    await request(`/dev/payment-orders/${encodeURIComponent(paymentOrder.paymentNo)}/succeed`, {
      method: 'POST',
      headers: adminHeaders(),
    })
    await request(`/dev/payment-orders/${encodeURIComponent(paymentOrder.paymentNo)}/succeed`, {
      method: 'POST',
      headers: adminHeaders(),
    })
  }

  const user = await request('/account/me', { headers: bearer(token) })
  assert(user.membershipLevel === plan.level, `会员等级应升级为 ${plan.level}`)
  const { items } = await request('/account/entitlements', { headers: bearer(token) })
  const orderEntitlements = items.filter((item) => item.orderId === order.id && item.entitlementType === 'membership')
  assert(orderEntitlements.length === 1, '支付幂等应只生成一条会员权益')
}

async function assertRecommendationEmptySlotsDoNotFallback() {
  const adminSlots = await request('/admin/site/recommendation-slots', { headers: adminHeaders() })
  const emptySlot = (adminSlots.items || []).find((item) => !item.sourceId)
  if (!emptySlot) {
    console.log('· 跳过推荐位空槽公开断言：当前所有推荐位均已配置内容')
    return
  }

  const publicSlots = await request(`/site/recommendations?placements=${encodeURIComponent(emptySlot.placementKey)}`)
  const visibleSlots = publicSlots.placements?.[emptySlot.placementKey] || []
  assert(
    !visibleSlots.some((item) => item.slotKey === emptySlot.slotKey && !item.sourceId),
    `公开推荐位不应为 ${emptySlot.placementKey}/${emptySlot.slotKey} 空槽返回兜底内容`,
  )
  console.log('✓ 推荐位空槽不会在公开接口兜底展示')
}

async function payIfNeeded(token, order) {
  if (order.status !== 'pending_payment') return order
  const { paymentOrder } = await request('/payment-orders', {
    method: 'POST',
    headers: bearer(token),
    body: JSON.stringify({ orderNo: order.orderNo, channel: 'dev' }),
  })
  const paid = await request(`/dev/payment-orders/${encodeURIComponent(paymentOrder.paymentNo)}/succeed`, {
    method: 'POST',
    headers: adminHeaders(),
  })
  await request(`/dev/payment-orders/${encodeURIComponent(paymentOrder.paymentNo)}/succeed`, {
    method: 'POST',
    headers: adminHeaders(),
  })
  return paid.order
}

async function assertDataServiceClosure() {
  const { token } = await createUser('registered', 20)
  const created = await request('/data-service-requests', {
    method: 'POST',
    headers: bearer(token),
    body: JSON.stringify({
      company: '业务闭环回归单位',
      organizationType: '国有企业',
      contactName: '数据服务测试人',
      contactPhone: uniquePhone(21),
      city: '大连',
      dataTopic: '产业企业名录回归包',
      serviceType: '企业名录梳理',
      regionScope: '大连市',
      timeRange: '近三年',
      industryScene: '数字经济',
      expectedFormat: 'Excel',
      dataVolume: '单主题数据包',
      usagePurpose: '内部研究',
      requirement: '需要字段说明、数据来源和可下载样表。',
      expectedResult: '交付 Excel 数据包、字段字典和结果说明。',
      budgetRange: '1万以下',
      preferredContactTime: '工作日',
    }),
  })
  assert(created.id && created.status === 'pending_contact', '数据服务申请创建失败')

  const adminList = await request(`/admin/data-service-requests?keyword=${encodeURIComponent(created.requestNo)}`, {
    headers: adminHeaders(),
  })
  assert(adminList.items?.some((item) => item.id === created.id) && adminList.summary?.total >= 1, '后台数据服务列表未返回新申请')

  try {
    await request(`/admin/data-service-requests/${encodeURIComponent(created.id)}`, {
      method: 'PATCH',
      headers: adminHeaders(),
      body: JSON.stringify({ status: 'in_service' }),
    })
    throw new Error('未付款数据服务不应允许进入服务中')
  } catch (error) {
    assert(error.status === 409 || error.code === 'data_service_payment_required', '未付款进入服务中应返回业务冲突')
  }

  const quoted = await request(`/admin/data-service-requests/${encodeURIComponent(created.id)}`, {
    method: 'PATCH',
    headers: adminHeaders(),
    body: JSON.stringify({
      accept: true,
      quotedTitle: '产业企业名录回归包',
      quotedAmount: 12000,
      quotedNote: '按字段字典和样表交付。',
      solutionSummary: '先确认口径，再整理企业名录和字段说明。',
    }),
  })
  assert(quoted.status === 'quoted' && quoted.quotedAmount === 12000, '数据服务报价未成功')

  const adminDetail = await request(`/admin/data-service-requests/${encodeURIComponent(created.requestNo)}`, {
    headers: adminHeaders(),
  })
  assert(adminDetail.id === created.id && adminDetail.status === 'quoted', '后台数据服务详情未返回报价状态')

  const order = await request(`/account/data-service-requests/${encodeURIComponent(created.id)}/checkout`, {
    method: 'POST',
    headers: bearer(token),
  })
  assert(order.items?.length === 1 && order.items[0].productId === 'data-service-custom', '数据服务订单明细不正确')
  await payIfNeeded(token, order)
  const inService = await request(`/account/data-service-requests/${encodeURIComponent(created.id)}`, { headers: bearer(token) })
  assert(inService.status === 'in_service', '数据服务支付后应进入服务中')

  const uploaded = await requestRaw('/staff/uploads/raw', {
    method: 'POST',
    headers: {
      ...adminHeaders(),
      'x-file-name': encodeURIComponent('data-service-regression.txt'),
      'content-type': 'text/plain',
    },
    body: Buffer.from('数据服务回归交付包'),
  })
  assert(uploaded.id, '数据服务交付文件上传失败')

  const completed = await request(`/staff/data-service-requests/${encodeURIComponent(created.id)}`, {
    method: 'PATCH',
    headers: adminHeaders(),
    body: JSON.stringify({
      status: 'completed',
      resultSummary: '已交付数据包、字段字典和来源说明。',
      deliverableFileId: uploaded.id,
    }),
  })
  assert(completed.status === 'completed' && completed.deliverableFileId === uploaded.id, '数据服务完成状态或交付文件不正确')
  const accountView = await request(`/account/data-service-requests/${encodeURIComponent(created.id)}`, { headers: bearer(token) })
  assert(accountView.status === 'completed' && accountView.resultSummary, '用户端未看到数据服务完成结果')

  const userDetail = await request(`/admin/users/${encodeURIComponent(created.userId)}`, { headers: adminHeaders() })
  assert(userDetail.user?.id === created.userId && Array.isArray(userDetail.courseRegistrations) && Array.isArray(userDetail.eventRegistrations), '后台用户详情应可读取课程/活动报名数组')
}

async function main() {
  console.log(`业务闭环回归：${apiBase}`)
  await assertRolePermissions()
  await assertPermissionDenials()
  await assertMembershipMatrix()
  await assertMembershipPaymentIdempotent()
  await assertRecommendationEmptySlotsDoNotFallback()
  await assertDataServiceClosure()
  console.log('业务闭环回归通过')
}

main().catch((error) => {
  console.error(error.stack || error.message)
  process.exit(1)
})
