import { execFileSync } from 'node:child_process'

const apiBase = (process.env.API_BASE || '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 runIp = process.env.TEST_CLIENT_IP || `127.43.${Math.max(1, Date.now() % 240)}.${Math.max(1, process.pid % 240)}`

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

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 `138${String(seed).padStart(8, '0')}`
}

function latestLocalPhoneCode(phone) {
  if (process.env.NODE_ENV === 'production') 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 codeFromSendResult(phone, sent) {
  return sent.demoCode || latestLocalPhoneCode(phone) || process.env.DEV_AUTH_CODE || '123456'
}

async function request(path, options = {}) {
  const headers = {
    'content-type': 'application/json',
    'x-forwarded-for': runIp,
    ...(options.headers || {}),
  }
  const response = await fetch(`${apiBase}${path}`, { ...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?.error || data?.code
    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?.error || data?.code
    error.data = data
    throw error
  }
  return data
}

async function expectHttp(label, expectedStatus, action) {
  try {
    await action()
  } catch (error) {
    assert(error.status === expectedStatus, `${label} 应返回 ${expectedStatus}，实际 ${error.status || error.code || error.message}`)
    return error
  }
  throw new Error(`${label} 应返回 ${expectedStatus}`)
}

async function loginUser(phone) {
  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 createUser(label, offset = 0) {
  const phone = uniquePhone(offset)
  const created = await request('/admin/users', {
    method: 'POST',
    headers: adminHeaders(),
    body: JSON.stringify({
      phone,
      displayName: `${runId}-${label}`,
      organization: `${runId} 测试单位`,
      city: '大连',
      onboardingCompleted: true,
      status: 'active',
    }),
  })
  const login = await loginUser(phone)
  return { ...login, createdUser: created, phone, passportNo: created.passportNo }
}

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(),
  })
  return paid.order || order
}

function accountEntryId(href = '') {
  const [, id = ''] = String(href).match(/^\/account\/(?:courses|events|data|consulting|expert-bookings|delivery)\/([^/?#]+)/) || []
  return decodeURIComponent(id)
}

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

async function ensureRegisteredOrderableCourse() {
  const courses = await request('/courses/catalog')
  const orderable = (courses.items || []).filter((item) => {
    const published = item.status === 'published' || item.productStatus === 'published'
    return published && item.registeredAccess !== 'closed' && catalogProductId(item)
  })
  const existing = orderable.find((item) => item.registeredAccess === 'paid' && Number(item.registeredPriceAmount || item.priceAmount || 0) > 0)
    || orderable.find((item) => item.registeredAccess === 'free')
    || orderable[0]
  if (existing) return existing

  const slug = `${runId.toLowerCase().replace(/[^a-z0-9-]/g, '-')}-order-entry-course`
  return request('/admin/course-products', {
    method: 'POST',
    headers: adminHeaders(),
    body: JSON.stringify({
      slug,
      title: `${runId} 订单入口课程`,
      summary: '验证订单详情跳转到真实课程报名',
      status: 'published',
      productStatus: 'published',
      instructor: '回归测试',
      courseStatus: 'anytime',
      courseFormat: '线上课程',
      registeredAccess: 'free',
      juniorAccess: 'free',
      seniorAccess: 'free',
      registeredPriceAmount: 0,
      juniorPriceAmount: 0,
      seniorPriceAmount: 0,
      lessonCount: 1,
      lessonModules: [{ title: '回归测试课时', sortOrder: 10, status: 'published' }],
      sortOrder: 9000,
    }),
  })
}

async function assertSystemConfigChecks() {
  const checks = await request('/admin/system/config-checks', { headers: adminHeaders() })
  const keys = new Set((checks.items || []).map((item) => item.key))
  for (const key of ['cors', 'rate_limit', 'api_internal_url', 'object_storage', 'wechat_pay']) {
    assert(keys.has(key), `配置检查缺少 ${key}`)
  }
  console.log('✓ 生产风险配置检查项可读')
}

async function assertOrderInvoiceAndDeepLink() {
  const user = await createUser('invoice-order', 1)
  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(user.token),
    body: JSON.stringify({ items: [{ productId: plan.productId, quantity: 1, billingCycle: 'monthly' }] }),
  })
  const detailBeforePay = await request(`/account/orders/${encodeURIComponent(order.orderNo)}`, { headers: bearer(user.token) })
  const itemBeforePay = detailBeforePay.items?.[0]
  assert(itemBeforePay?.serviceEntryType === 'membership', '订单项缺少会员服务入口类型')
  assert(itemBeforePay?.serviceEntryHref === '/account/membership', '会员订单项应跳转个人中心会员页')

  if (order.status === 'pending_payment') {
    await expectHttp('未支付订单申请发票', 400, () => request(`/account/orders/${encodeURIComponent(order.orderNo)}/invoice-application`, {
      method: 'POST',
      headers: bearer(user.token),
      body: JSON.stringify({ title: `${runId} 未支付`, email: 'regression@example.com' }),
    }))
  }

  const paidOrder = await payIfNeeded(user.token, order)
  const detail = await request(`/account/orders/${encodeURIComponent(paidOrder.orderNo || order.orderNo)}`, { headers: bearer(user.token) })
  assert(['paid', 'completed'].includes(detail.status), '订单支付后详情深链未返回已支付状态')
  assert(detail.items?.every((item) => item.serviceEntryType && item.serviceEntryHref), '订单详情所有 item 都必须有服务入口字段')

  const invoice = await request(`/account/orders/${encodeURIComponent(detail.orderNo)}/invoice-application`, {
    method: 'POST',
    headers: bearer(user.token),
    body: JSON.stringify({
      title: `${runId} 发票抬头`,
      taxNumber: '91210200MA0REGTEST',
      email: 'regression@example.com',
      remark: runId,
    }),
  })
  assert(invoice.invoiceNo && invoice.status === 'pending', '发票申请未创建为 pending')
  await expectHttp('重复申请同一订单发票', 409, () => request(`/account/orders/${encodeURIComponent(detail.orderNo)}/invoice-application`, {
    method: 'POST',
    headers: bearer(user.token),
    body: JSON.stringify({ title: `${runId} 重复`, email: 'regression@example.com' }),
  }))

  await expectHttp('未上传文件直接标记已开票', 400, () => request(`/admin/invoice-applications/${encodeURIComponent(invoice.invoiceNo)}`, {
    method: 'PATCH',
    headers: adminHeaders(),
    body: JSON.stringify({ status: 'issued', providerInvoiceId: `${runId}-NOFILE`, issuedAt: new Date().toISOString() }),
  }))

  await requestRaw(`/admin/invoice-applications/${encodeURIComponent(invoice.invoiceNo)}/file`, {
    method: 'POST',
    headers: {
      ...adminHeaders(),
      'x-file-name': encodeURIComponent(`${runId}.pdf`),
      'content-type': 'application/pdf',
    },
    body: Buffer.from('%PDF-1.4\n% regression invoice\n'),
  })
  await request(`/admin/invoice-applications/${encodeURIComponent(invoice.invoiceNo)}`, {
    method: 'PATCH',
    headers: adminHeaders(),
    body: JSON.stringify({ status: 'processing', adminNote: runId }),
  })
  const issued = await request(`/admin/invoice-applications/${encodeURIComponent(invoice.invoiceNo)}`, {
    method: 'PATCH',
    headers: adminHeaders(),
    body: JSON.stringify({
      status: 'issued',
      providerInvoiceId: `${runId}-INV`,
      issuedBy: 'REGRESSION',
      issuedAt: new Date().toISOString(),
    }),
  })
  assert(issued.status === 'issued' && issued.pdfFileId, '后台开票后状态和文件必须可见')
  const accountInvoice = await request(`/account/invoice-applications/${encodeURIComponent(invoice.invoiceNo)}`, { headers: bearer(user.token) })
  assert(accountInvoice.status === 'issued' && accountInvoice.fileUrl, '用户端发票详情未看到已开票文件')
  console.log('✓ 订单深链、服务入口字段、发票申请/开票闭环通过')
}

async function assertOrderFulfillmentEntryDeepLinks() {
  const user = await createUser('order-entry', 15)

  const course = await ensureRegisteredOrderableCourse()
  const courseProductId = catalogProductId(course)
  assert(courseProductId, '缺少可下单课程')
  const courseOrder = await request('/orders', {
    method: 'POST',
    headers: bearer(user.token),
    body: JSON.stringify({ items: [{ productId: courseProductId, quantity: 1 }] }),
  })
  assert(courseOrder.items?.[0]?.productId === courseProductId, '课程下单必须使用真实 productId')
  const paidCourseOrder = await payIfNeeded(user.token, courseOrder)
  const courseDetail = await request(`/account/orders/${encodeURIComponent(paidCourseOrder.orderNo || courseOrder.orderNo)}`, { headers: bearer(user.token) })
  const courseItem = (courseDetail.items || []).find((item) => item.productType === 'course')
  const courseRegistrationId = accountEntryId(courseItem?.serviceEntryHref)
  assert(courseItem?.serviceEntryType === 'course', '课程订单项缺少课程服务入口类型')
  assert([courseProductId, courseRegistrationId].includes(courseItem.serviceEntryId || courseProductId), '课程订单项服务入口 ID 不正确')
  assert(courseRegistrationId, '课程订单项服务入口 href 不正确')
  const courseRegistrations = await request('/account/course-registrations', { headers: bearer(user.token) })
  const courseRegistration = (courseRegistrations.items || []).find((item) => item.productId === courseProductId)
  assert(courseRegistration?.id, '课程支付后应生成真实报名记录')
  assert([courseProductId, courseRegistration.id].includes(courseRegistrationId), '课程订单入口应指向商品或真实报名记录')
  const courseRegistrationDetail = await request(`/account/course-registrations/${encodeURIComponent(courseRegistration.id)}`, { headers: bearer(user.token) })
  assert(courseRegistrationDetail.id === courseRegistration.id && courseRegistrationDetail.productId === courseProductId, '课程订单入口无法打开真实报名详情')

  const startsAt = new Date(Date.now() + 5 * 24 * 60 * 60 * 1000)
  const endsAt = new Date(startsAt.getTime() + 2 * 60 * 60 * 1000)
  const event = await request('/admin/events', {
    method: 'POST',
    headers: adminHeaders(),
    body: JSON.stringify({
      slug: `${runId.toLowerCase().replace(/[^a-z0-9-]/g, '-')}-order-entry-event`,
      title: `${runId} 订单入口活动`,
      summary: '验证订单详情跳转到真实活动报名',
      status: 'published',
      productStatus: 'published',
      registeredAccess: 'free',
      juniorAccess: 'free',
      seniorAccess: 'free',
      registeredPriceAmount: 0,
      juniorPriceAmount: 0,
      seniorPriceAmount: 0,
      startsAt: startsAt.toISOString(),
      endsAt: endsAt.toISOString(),
      location: '大连',
      capacity: 20,
    }),
  })
  assert(event.id, '后台活动创建失败')
  const eventOrder = await request('/orders', {
    method: 'POST',
    headers: bearer(user.token),
    body: JSON.stringify({ items: [{ productId: event.id, quantity: 1 }] }),
  })
  assert(eventOrder.items?.[0]?.productId === event.id, '活动下单必须使用真实 productId')
  const paidEventOrder = await payIfNeeded(user.token, eventOrder)
  const eventDetail = await request(`/account/orders/${encodeURIComponent(paidEventOrder.orderNo || eventOrder.orderNo)}`, { headers: bearer(user.token) })
  const eventItem = (eventDetail.items || []).find((item) => item.productType === 'event')
  const eventRegistrationId = accountEntryId(eventItem?.serviceEntryHref)
  assert(eventItem?.serviceEntryType === 'event', '活动订单项缺少活动服务入口类型')
  assert(eventRegistrationId, '活动订单项服务入口 href 不正确')
  const eventRegistrations = await request('/account/event-registrations', { headers: bearer(user.token) })
  const eventRegistration = (eventRegistrations.items || []).find((item) => item.productId === event.id)
  assert(eventRegistration?.id, '活动支付后应生成真实报名记录')
  assert([event.id, eventRegistration.id].includes(eventRegistrationId), '活动订单入口应指向商品或真实报名记录')
  const eventRegistrationDetail = await request(`/account/event-registrations/${encodeURIComponent(eventRegistration.id)}`, { headers: bearer(user.token) })
  assert(eventRegistrationDetail.id === eventRegistration.id && eventRegistrationDetail.productId === event.id, '活动订单入口无法打开真实报名详情')

  console.log('✓ 课程/活动订单服务入口指向真实报名详情')
}

async function assertActivityManualRegistrationAndCheckin() {
  const user = await createUser('event-checkin', 20)
  const slug = `${runId.toLowerCase().replace(/[^a-z0-9-]/g, '-')}-event`
  const startsAt = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000)
  const endsAt = new Date(startsAt.getTime() + 2 * 60 * 60 * 1000)
  const event = await request('/admin/events', {
    method: 'POST',
    headers: adminHeaders(),
    body: JSON.stringify({
      slug,
      title: `${runId} 活动闭环`,
      summary: '平台闭环回归活动',
      status: 'published',
      productStatus: 'published',
      registeredAccess: 'free',
      juniorAccess: 'free',
      seniorAccess: 'free',
      startsAt: startsAt.toISOString(),
      endsAt: endsAt.toISOString(),
      location: '大连',
      capacity: 1,
    }),
  })
  assert(event.id, '后台活动创建失败')

  const registration = await request(`/admin/events/${encodeURIComponent(event.id)}/registrations`, {
    method: 'POST',
    headers: adminHeaders(),
    body: JSON.stringify({
      passportNo: user.passportNo,
      source: 'regression_manual',
      note: runId,
    }),
  })
  assert(registration.status === 'registered' && registration.checkinCode, '活动补录应生成正式报名和核销码')

  const checked = await request(`/staff/event-registrations/${encodeURIComponent(registration.id)}`, {
    method: 'POST',
    headers: adminHeaders(),
    body: JSON.stringify({ eventId: event.id, source: 'regression_manual' }),
  })
  assert(checked.status === 'checked_in' && checked.alreadyCheckedIn === false, '活动手动核销失败')
  const checkedAgain = await request(`/staff/event-registrations/${encodeURIComponent(registration.id)}`, {
    method: 'POST',
    headers: adminHeaders(),
    body: JSON.stringify({ eventId: event.id, source: 'regression_manual' }),
  })
  assert(checkedAgain.alreadyCheckedIn === true, '重复核销应返回 alreadyCheckedIn')
  await expectHttp('已核销报名不可取消', 409, () => request(`/admin/event-registrations/${encodeURIComponent(registration.id)}/cancel`, {
    method: 'POST',
    headers: adminHeaders(),
    body: JSON.stringify({ reason: runId }),
  }))
  console.log('✓ 活动补录、通行证报名、手动核销、取消拦截通过')
}

async function assertConsultingClosure() {
  const user = await createUser('consulting', 40)
  const consultingPage = await request('/site/consulting')
  const direction = (consultingPage.directions || []).find((item) => item.status === 'published')?.title
    || consultingPage.directions?.[0]?.title
  assert(direction, '缺少可提交的规划咨询方向')
  const created = await request('/consulting-requests', {
    method: 'POST',
    headers: bearer(user.token),
    body: JSON.stringify({
      company: `${runId} 咨询单位`,
      contactName: '回归测试',
      contactPhone: user.phone,
      email: 'consulting@example.com',
      direction,
      organizationType: '企业',
      industry: '数字经济',
      city: '大连',
      projectStage: '前期调研',
      urgency: '1个月内',
      budgetRange: '5-10万',
      expectedStart: '近期',
      currentSituation: '需要梳理规划路径',
      requirement: '输出规划建议和报价闭环',
      goals: '明确建设路径',
      preferredContactTime: '工作日',
    }),
  })
  assert(created.id && created.status === 'pending', '规划咨询申请创建失败')

  const quoted = await request(`/admin/consulting-requests/${encodeURIComponent(created.id)}`, {
    method: 'PATCH',
    headers: adminHeaders(),
    body: JSON.stringify({
      accept: true,
      quotedTitle: `${runId} 咨询报价`,
      quotedAmount: 10000,
      quotedNote: '回归测试报价',
      chargeRequired: true,
    }),
  })
  assert(quoted.status === 'proposal_ready' && quoted.quotedAmount === 10000, '规划咨询报价失败')
  await expectHttp('未付款咨询不能直接完成', 409, () => request(`/admin/consulting-requests/${encodeURIComponent(created.id)}`, {
    method: 'PATCH',
    headers: adminHeaders(),
    body: JSON.stringify({ status: 'in_service' }),
  }))

  const order = await request(`/account/consulting-requests/${encodeURIComponent(created.id)}/checkout`, {
    method: 'POST',
    headers: bearer(user.token),
  })
  const item = order.items?.[0]
  assert(item?.serviceEntryType === 'consulting' && item.serviceEntryHref === `/account/consulting/${created.id}`, '咨询订单缺少个人中心服务入口')
  await payIfNeeded(user.token, order)

  const completed = await request(`/admin/consulting-requests/${encodeURIComponent(created.id)}`, {
    method: 'PATCH',
    headers: adminHeaders(),
    body: JSON.stringify({
      status: 'completed',
      finalSummary: '已完成规划咨询回归测试总结。',
    }),
  })
  assert(completed.status === 'completed' && completed.finalSummary, '规划咨询完成失败')
  console.log('✓ 规划咨询申请、报价、付款、完成闭环通过')
}

async function assertExpertBookingClosure() {
  const expertPhone = uniquePhone(70)
  const slug = `${runId.toLowerCase().replace(/[^a-z0-9-]/g, '-')}-expert`
  const expert = await request('/admin/experts', {
    method: 'POST',
    headers: adminHeaders(),
    body: JSON.stringify({
      name: `${runId} 专家`,
      slug,
      phone: expertPhone,
      contactPhone: expertPhone,
      title: '回归专家',
      organization: `${runId} 专家机构`,
      directions: ['数字化转型'],
      bio: '闭环回归专家',
      priceAmount: 1000,
      shareRatio: 80,
      status: 'published',
      availabilityStatus: 'this-week',
      acceptingOrders: true,
      createUser: true,
    }),
  })
  assert(expert.id, '专家创建失败')
  const expertAccount = await loginUser(expertPhone)
  const user = await createUser('expert-booking', 80)
  const startsAt = new Date(Date.now() + 4 * 24 * 60 * 60 * 1000)
  startsAt.setUTCHours(2, 0, 0, 0)
  const endsAt = new Date(startsAt.getTime() + 60 * 60 * 1000)
  const slot = await request('/admin/expert-slots', {
    method: 'POST',
    headers: adminHeaders(),
    body: JSON.stringify({
      expertId: expert.id,
      startsAt: startsAt.toISOString(),
      endsAt: endsAt.toISOString(),
      status: 'open',
      note: runId,
    }),
  })
  assert(slot.status === 'open', '专家排期创建失败')

  const publicSlots = await request(`/experts/${encodeURIComponent(expert.id)}/slots`)
  assert((publicSlots.items || []).some((item) => item.id === slot.id), '用户端未显示专家开放时段')

  const booking = await request('/account/expert-bookings', {
    method: 'POST',
    headers: bearer(user.token),
    body: JSON.stringify({
      slotId: slot.id,
      contactName: '专家预约回归用户',
      contactPhone: user.phone,
      background: runId,
      question: '验证专家履约闭环',
    }),
  })
  assert(booking.status === 'pending_payment', '专家预约应先进入待支付锁位')

  const order = await request('/orders', {
    method: 'POST',
    headers: bearer(user.token),
    body: JSON.stringify({
      expertBookingId: booking.id,
      items: [{ productId: 'expert-city-consulting', quantity: 1, expertBookingId: booking.id, expertName: expert.name }],
    }),
  })
  assert(order.items?.[0]?.serviceEntryHref === `/account/expert-bookings/${booking.id}`, '专家订单缺少预约详情入口')
  await payIfNeeded(user.token, order)
  const paidBooking = await request(`/account/expert-bookings/${encodeURIComponent(booking.id)}`, { headers: bearer(user.token) })
  assert(paidBooking.status === 'pending_expert', '专家预约支付后应进入待专家确认')

  await expectHttp('专家确认预约必须填写会议方式', 400, () => request(`/account/expert-bookings/${encodeURIComponent(booking.id)}`, {
    method: 'PATCH',
    headers: bearer(expertAccount.token),
    body: JSON.stringify({ status: 'confirmed' }),
  }))
  const confirmed = await request(`/account/expert-bookings/${encodeURIComponent(booking.id)}`, {
    method: 'PATCH',
    headers: bearer(expertAccount.token),
    body: JSON.stringify({ status: 'confirmed', meetingMethod: '腾讯会议', meetingNote: runId }),
  })
  assert(confirmed.status === 'confirmed', '专家确认预约失败')
  const submitted = await request(`/account/expert-bookings/${encodeURIComponent(booking.id)}`, {
    method: 'PATCH',
    headers: bearer(expertAccount.token),
    body: JSON.stringify({ status: 'pending_user_confirm', meetingNote: `${runId} 完成提交` }),
  })
  assert(submitted.status === 'pending_user_confirm', '专家提交完成失败')
  const completed = await request(`/account/expert-bookings/${encodeURIComponent(booking.id)}/confirm-completion`, {
    method: 'POST',
    headers: bearer(user.token),
  })
  assert(completed.status === 'completed', '用户确认专家预约完成失败')
  console.log('✓ 专家排期、锁位、支付、确认、完成、结算触发闭环通过')
}

async function assertMembershipReportsAndEntitlements() {
  const user = await createUser('membership-report', 100)
  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(user.token),
    body: JSON.stringify({ items: [{ productId: plan.productId, quantity: 1, billingCycle: 'monthly' }] }),
  })
  await payIfNeeded(user.token, order)
  const membership = await request('/account/membership', { headers: bearer(user.token) })
  assert(['junior', 'senior'].includes(membership.membership?.level || membership.level), '会员开通后状态未刷新')
  const entitlements = await request(`/admin/membership/entitlements?search=${encodeURIComponent(user.phone)}`, { headers: adminHeaders() })
  assert(Array.isArray(entitlements.items), '后台权益额度账户接口不可读')

  const reportFile = await requestRaw('/admin/uploads/raw', {
    method: 'POST',
    headers: {
      ...adminHeaders(),
      'x-file-name': encodeURIComponent(`${runId}-membership-report.pdf`),
      'content-type': 'application/pdf',
      'x-owner-type': 'cms_asset',
    },
    body: Buffer.from(`%PDF-1.4\n% ${runId} membership report\n`),
  })
  assert(reportFile.id, '会员报告文件上传失败')

  const report = await request('/admin/membership/content-deliveries', {
    method: 'POST',
    headers: adminHeaders(),
    body: JSON.stringify({
      title: `${runId} 会员报告`,
      contentType: 'monthly_report',
      eligibleTiers: ['junior', 'senior'],
      periodKey: runId,
      fileId: reportFile.id,
      href: `/reports/${runId}`,
      status: 'published',
    }),
  })
  assert(report.id && report.status === 'published', '会员报告发布失败')
  const reports = await request('/admin/membership/content-deliveries?status=published', { headers: adminHeaders() })
  assert((reports.items || []).some((item) => item.id === report.id), '后台会员报告列表未返回新发布报告')
  console.log('✓ 会员状态、权益额度账户、会员报告发布检查通过')
}

async function main() {
  console.log(`平台闭环回归：${apiBase}`)
  console.log(`runId=${runId}`)
  await assertSystemConfigChecks()
  await assertOrderInvoiceAndDeepLink()
  await assertOrderFulfillmentEntryDeepLinks()
  await assertActivityManualRegistrationAndCheckin()
  await assertConsultingClosure()
  await assertExpertBookingClosure()
  await assertMembershipReportsAndEntitlements()
  console.log('平台闭环回归通过')
}

main().catch((error) => {
  console.error(error.stack || error.message)
  if (error.data) console.error(JSON.stringify(error.data, null, 2))
  process.exit(1)
})
