import { test, expect } from '@playwright/test';

/**
 * 单据完整流程测试
 * 覆盖：散客做单、会员做单、权益购买、权益核销、订单管理、预约
 */

const BASE = process.env.BASE_URL || 'http://192.168.3.151:5173';
const EMAIL = 'echo@shboka.com';
const PASSWORD = '123456';
const STORE_ID = '01KSSFPT0ZXXHE6PRVXY9RE1K2';
const ADA_ID = '01KXD532QWXQTM57HT5BTEJWGS'; // Ada 876542211111
const SVC_PURE = '01KT3VBKHP7V3D6JFX9F916ZF8'; // 纯色美甲 ¥88
const SVC_GRADIENT = '01KT3VBKHPTV2NTMPSF1XJ7HQC'; // 渐变美甲 ¥138
const SVC_SHELL = '01KT3VBKHPK4SQ2EE59DNQYVDG'; // 韩式贝壳片 ¥228
const SVC_FOOT = '01KT3VBKHPK9N0AJWCR9FAJTA0'; // 基础足部护理 ¥88
const EMPLOYEE_ID = '01KT5RH5Z9ZT6T5VAP8XM15FFX'; // 周小雨
const SV_PLAN_100 = '01KTGSV000000000000000001'; // 入门储值100
const SV_PLAN_200 = '01KTGSV000000000000000002'; // 储值200送10
const VC_PURE_5 = '01KTGVC000000000000000001'; // 纯色美甲5次卡

let token: string;
let sessionId: string;

function headers() {
  return { 'Product': 'inail', 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' };
}

function today() {
  return new Date().toISOString().split('T')[0];
}

function makeTransaction(overrides: Record<string, unknown> = {}) {
  return {
    pos_session_id: sessionId,
    items: [],
    goods_items: [],
    tip_cents: 0,
    service_charge_cents: 0,
    discount_cents: 0,
    order_date: today(),
    method: 'cash',
    split_payments: [{ method: 'cash', amount_cents: 0 }],
    stored_value_splits: [],
    ...overrides,
  };
}

function serviceItem(id: string, name: string, price: number) {
  return { type: 'service', reference_id: id, name, quantity: 1, unit_price_cents: price };
}

test.beforeAll(async ({ request }) => {
  const loginRes = await request.post(`${BASE}/nail/auth/login`, {
    data: { email: EMAIL, password: PASSWORD },
    headers: { 'Product': 'inail' },
  });
  expect(loginRes.status()).toBe(200);
  token = (await loginRes.json()).access_token;

  await request.post(`${BASE}/nail/auth/select-store`, {
    data: { org_id: STORE_ID },
    headers: headers(),
  });

  const sessionRes = await request.get(`${BASE}/nail/pos/sessions/current`, {
    headers: headers(),
  });
  if (sessionRes.status() === 200) {
    sessionId = (await sessionRes.json()).id;
  } else {
    // 如果没有 session，尝试开班
    const openRes = await request.post(`${BASE}/nail/pos/sessions`, {
      data: { opening_balance_cents: 0 },
      headers: headers(),
    });
    if (openRes.status() === 200 || openRes.status() === 201) {
      sessionId = (await openRes.json()).id;
    } else {
      // 409 = 已有 session，从错误里拿不到 id，用固定值
      sessionId = 'unknown';
    }
  }
});

// ═══════════════════════════════════════════════════
// 一、散客做单
// ═══════════════════════════════════════════════════
test.describe('散客做单', () => {

  test('单服务 - 现金结账', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        split_payments: [{ method: 'cash', amount_cents: 8800 }],
      }),
    });
    expect(res.status()).toBe(201);
    const data = await res.json();
    expect(data.status).toBe('completed');
    expect(data.total_cents).toBe(8800);
    expect(data.method).toBe('cash');
  });

  test('多服务 - 现金结账', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        items: [
          serviceItem(SVC_PURE, '纯色美甲', 8800),
          serviceItem(SVC_GRADIENT, '渐变美甲', 13800),
        ],
        split_payments: [{ method: 'cash', amount_cents: 22600 }],
      }),
    });
    expect(res.status()).toBe(201);
    const data = await res.json();
    expect(data.total_cents).toBe(22600);
  });

  test('带小费 - 现金结账（10%）', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        tip_cents: 880,
        split_payments: [{ method: 'cash', amount_cents: 9680 }],
      }),
    });
    expect(res.status()).toBe(201);
    const data = await res.json();
    expect(data.total_cents).toBe(9680);
  });

  test('带小费 - 现金结账（自定义金额）', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        tip_cents: 2000,
        split_payments: [{ method: 'cash', amount_cents: 10800 }],
      }),
    });
    expect(res.status()).toBe(201);
    const data = await res.json();
    expect(data.total_cents).toBe(10800);
  });

  test('微信支付', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        method: 'wechat_pay',
        split_payments: [{ method: 'wechat_pay', amount_cents: 8800 }],
      }),
    });
    expect(res.status()).toBe(201);
  });

  test('支付宝支付', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        method: 'alipay',
        split_payments: [{ method: 'alipay', amount_cents: 8800 }],
      }),
    });
    expect(res.status()).toBe(201);
  });

  test('银行卡支付', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        method: 'bank_card',
        split_payments: [{ method: 'bank_card', amount_cents: 8800 }],
      }),
    });
    expect(res.status()).toBe(201);
  });

  test('混合支付（现金 + 微信）', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        items: [serviceItem(SVC_GRADIENT, '渐变美甲', 13800)],
        method: 'split',
        split_payments: [
          { method: 'cash', amount_cents: 5000 },
          { method: 'wechat_pay', amount_cents: 8800 },
        ],
      }),
    });
    expect(res.status()).toBe(201);
  });

  test('带折扣结账', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        discount_cents: 1000,
        split_payments: [{ method: 'cash', amount_cents: 7800 }],
      }),
    });
    expect(res.status()).toBe(201);
    const data = await res.json();
    expect(data.total_cents).toBe(7800);
  });

  test('金额为0应报错', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        split_payments: [{ method: 'cash', amount_cents: 0 }],
      }),
    });
    // 金额不匹配应该报错，不应该 201
    expect(res.status()).not.toBe(201);
    expect(res.status()).not.toBe(500);
  });

  test('空 items 应报错', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        items: [],
        split_payments: [{ method: 'cash', amount_cents: 0 }],
      }),
    });
    expect(res.status()).not.toBe(500);
  });

  test('无效 service_id 应报错', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        items: [serviceItem('invalid_id', '不存在的服务', 8800)],
        split_payments: [{ method: 'cash', amount_cents: 8800 }],
      }),
    });
    expect(res.status()).not.toBe(500);
  });
});

// ═══════════════════════════════════════════════════
// 二、会员做单
// ═══════════════════════════════════════════════════
test.describe('会员做单', () => {

  test('会员 - 单服务现金结账', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        client_id: ADA_ID,
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        split_payments: [{ method: 'cash', amount_cents: 8800 }],
      }),
    });
    expect(res.status()).toBe(201);
    const data = await res.json();
    expect(data.status).toBe('completed');
  });

  test('会员 - 多服务结账', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        client_id: ADA_ID,
        items: [
          serviceItem(SVC_PURE, '纯色美甲', 8800),
          serviceItem(SVC_GRADIENT, '渐变美甲', 13800),
          serviceItem(SVC_FOOT, '基础足部护理', 8800),
        ],
        split_payments: [{ method: 'cash', amount_cents: 31400 }],
      }),
    });
    expect(res.status()).toBe(201);
    const data = await res.json();
    expect(data.total_cents).toBe(31400);
  });

  test('会员 - 带小费结账', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        client_id: ADA_ID,
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        tip_cents: 1760,
        split_payments: [{ method: 'cash', amount_cents: 10560 }],
      }),
    });
    expect(res.status()).toBe(201);
  });

  test('会员 - member_card 支付', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        client_id: ADA_ID,
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        method: 'member_card',
        split_payments: [{ method: 'member_card', amount_cents: 8800 }],
      }),
    });
    expect(res.status()).toBe(201);
  });

  test('会员 - 消费后 total_spent 和 visits 更新', async ({ request }) => {
    // 先查当前数据
    const before = await request.get(`${BASE}/nail/clients/${ADA_ID}`, { headers: headers() });
    const beforeData = await before.json();
    const beforeSpent = beforeData.total_spent_cents;
    const beforeVisits = beforeData.total_visits;

    // 做一单
    await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        client_id: ADA_ID,
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        split_payments: [{ method: 'cash', amount_cents: 8800 }],
      }),
    });

    // 验证数据更新
    const after = await request.get(`${BASE}/nail/clients/${ADA_ID}`, { headers: headers() });
    const afterData = await after.json();
    expect(afterData.total_spent_cents).toBe(beforeSpent + 8800);
    expect(afterData.total_visits).toBe(beforeVisits + 1);
  });

  test('会员 - debt 不为负', async ({ request }) => {
    const res = await request.get(`${BASE}/nail/clients/${ADA_ID}`, { headers: headers() });
    const data = await res.json();
    expect(data.debt_amount_cents).toBeGreaterThanOrEqual(0);
  });
});

// ═══════════════════════════════════════════════════
// 三、权益购买
// ═══════════════════════════════════════════════════
test.describe('权益购买', () => {

  test('购买储值方案 - 入门储值100', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        client_id: ADA_ID,
        goods_items: [{ goods_type: 'stored_value', goods_id: SV_PLAN_100, price_cents: 10000, name: '入门储值100', validity: { type: 'no_limit' } }],
        split_payments: [{ method: 'cash', amount_cents: 10000 }],
      }),
    });
    expect(res.status()).toBe(201);
    const data = await res.json();
    expect(data.status).toBe('completed');
    expect(data.total_cents).toBe(10000);
  });

  test('购买储值方案 - 储值200送10', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        client_id: ADA_ID,
        goods_items: [{ goods_type: 'stored_value', goods_id: SV_PLAN_200, price_cents: 20000, name: '储值200送10', validity: { type: 'no_limit' } }],
        split_payments: [{ method: 'cash', amount_cents: 20000 }],
      }),
    });
    expect(res.status()).toBe(201);
  });

  test('购买次卡 - 纯色美甲5次卡', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        client_id: ADA_ID,
        goods_items: [{ goods_type: 'visit_card', goods_id: VC_PURE_5, price_cents: 35800, name: '纯色美甲5次卡', validity: { type: 'purchase_day', unit: 'day', value: 365 } }],
        split_payments: [{ method: 'cash', amount_cents: 35800 }],
      }),
    });
    expect(res.status()).toBe(201);
  });

  test('购买权益 - 无 client_id 应报错', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        goods_items: [{ goods_type: 'stored_value', goods_id: SV_PLAN_100, price_cents: 10000, name: '入门储值100', validity: { type: 'no_limit' } }],
        split_payments: [{ method: 'cash', amount_cents: 10000 }],
      }),
    });
    // 购买权益必须关联会员，不应该成功
    expect(res.status()).not.toBe(500);
  });

  test('购买权益 - 无效 goods_id 应报错', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        client_id: ADA_ID,
        goods_items: [{ goods_type: 'stored_value', goods_id: 'invalid_plan_id', price_cents: 10000, name: '假方案', validity: { type: 'no_limit' } }],
        split_payments: [{ method: 'cash', amount_cents: 10000 }],
      }),
    });
    expect(res.status()).not.toBe(500);
    expect(res.status()).toBeGreaterThanOrEqual(400);
  });
});

// ═══════════════════════════════════════════════════
// 四、储值卡核销
// ═══════════════════════════════════════════════════
test.describe('储值卡核销', () => {

  test('储值卡全额抵扣 - 不应 500', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        client_id: ADA_ID,
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        method: 'stored_value',
        split_payments: [{ method: 'stored_value', amount_cents: 8800 }],
        stored_value_holding_id: 'test_holding',
        stored_value_splits: [{ holding_id: 'test_holding', amount_cents: 8800 }],
      }),
    });
    // 可能是 400（holding 不存在），但绝不应该是 500
    expect(res.status()).not.toBe(500);
  });

  test('储值卡部分抵扣（储值 + 现金混合）- 不应 500', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        client_id: ADA_ID,
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        method: 'split',
        split_payments: [
          { method: 'stored_value', amount_cents: 5000 },
          { method: 'cash', amount_cents: 3800 },
        ],
        stored_value_holding_id: 'test_holding',
        stored_value_splits: [{ holding_id: 'test_holding', amount_cents: 5000 }],
      }),
    });
    expect(res.status()).not.toBe(500);
  });

  test('储值卡 + 小费 - 不应 500', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        client_id: ADA_ID,
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        tip_cents: 880,
        method: 'stored_value',
        split_payments: [{ method: 'stored_value', amount_cents: 9680 }],
        stored_value_holding_id: 'test_holding',
        stored_value_splits: [{ holding_id: 'test_holding', amount_cents: 9680 }],
      }),
    });
    expect(res.status()).not.toBe(500);
  });

  test('储值卡余额不足 - 应返回 400 不是 500', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        client_id: ADA_ID,
        items: [serviceItem(SVC_SHELL, '韩式贝壳片', 22800)],
        method: 'stored_value',
        split_payments: [{ method: 'stored_value', amount_cents: 22800 }],
        stored_value_holding_id: 'test_holding',
        stored_value_splits: [{ holding_id: 'test_holding', amount_cents: 22800 }],
      }),
    });
    expect(res.status()).not.toBe(500);
  });

  test('储值卡 - 假 holding_id 应返回 4xx', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        client_id: ADA_ID,
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        method: 'stored_value',
        split_payments: [{ method: 'stored_value', amount_cents: 8800 }],
        stored_value_holding_id: 'fake_holding_id_12345',
        stored_value_splits: [{ holding_id: 'fake_holding_id_12345', amount_cents: 8800 }],
      }),
    });
    expect(res.status()).toBeGreaterThanOrEqual(400);
    expect(res.status()).toBeLessThan(500);
  });

  test('储值卡 - 无 client_id 应报错', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        method: 'stored_value',
        split_payments: [{ method: 'stored_value', amount_cents: 8800 }],
        stored_value_holding_id: 'test_holding',
        stored_value_splits: [{ holding_id: 'test_holding', amount_cents: 8800 }],
      }),
    });
    // 散客不能用储值卡
    expect(res.status()).not.toBe(500);
  });
});

// ═══════════════════════════════════════════════════
// 五、次卡核销
// ═══════════════════════════════════════════════════
test.describe('次卡核销', () => {

  test('次卡核销 - 假 holding_id 必须被拒绝', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        client_id: ADA_ID,
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        method: 'visit_card',
        split_payments: [{ method: 'visit_card', amount_cents: 8800 }],
        visit_card_splits: [{ holding_id: 'fake_id_should_fail', amount_cents: 8800 }],
      }),
    });
    // 假 ID 不能成功！这是安全漏洞
    expect(res.status()).toBeGreaterThanOrEqual(400);
  });

  test('次卡核销 - 空 splits 应报错', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        client_id: ADA_ID,
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        method: 'visit_card',
        split_payments: [{ method: 'visit_card', amount_cents: 8800 }],
        visit_card_splits: [],
      }),
    });
    expect(res.status()).not.toBe(500);
  });

  test('次卡核销 - 无 client_id 应报错', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        method: 'visit_card',
        split_payments: [{ method: 'visit_card', amount_cents: 8800 }],
        visit_card_splits: [{ holding_id: 'test', amount_cents: 8800 }],
      }),
    });
    expect(res.status()).not.toBe(500);
  });
});

// ═══════════════════════════════════════════════════
// 六、代金券核销
// ═══════════════════════════════════════════════════
test.describe('代金券核销', () => {

  test('代金券核销 - 假 voucher_id 必须被拒绝', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        client_id: ADA_ID,
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        method: 'voucher',
        split_payments: [{ method: 'voucher', amount_cents: 8800 }],
        voucher_splits: [{ voucher_id: 'fake_voucher_should_fail', amount_cents: 8800 }],
      }),
    });
    // 假 ID 不能成功！安全漏洞
    expect(res.status()).toBeGreaterThanOrEqual(400);
  });

  test('代金券核销 - 空 splits 应报错', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        client_id: ADA_ID,
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        method: 'voucher',
        split_payments: [{ method: 'voucher', amount_cents: 8800 }],
        voucher_splits: [],
      }),
    });
    expect(res.status()).not.toBe(500);
  });

  test('代金券 - 金额超过券面值应报错', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        client_id: ADA_ID,
        items: [serviceItem(SVC_SHELL, '韩式贝壳片', 22800)],
        method: 'voucher',
        split_payments: [{ method: 'voucher', amount_cents: 22800 }],
        voucher_splits: [{ voucher_id: 'fake_voucher', amount_cents: 22800 }],
      }),
    });
    expect(res.status()).not.toBe(500);
  });
});

// ═══════════════════════════════════════════════════
// 七、订单管理
// ═══════════════════════════════════════════════════
test.describe('订单管理', () => {

  test('查询订单列表 - 全部', async ({ request }) => {
    const res = await request.get(`${BASE}/nail/pos/orders?page=1&page_size=20`, {
      headers: headers(),
    });
    expect(res.status()).toBe(200);
    const data = await res.json();
    expect(data).toHaveProperty('orders');
    expect(data).toHaveProperty('total');
  });

  test('查询订单列表 - 按日期筛选', async ({ request }) => {
    const res = await request.get(`${BASE}/nail/pos/orders?page=1&page_size=20&date_from=${today()}&date_to=${today()}`, {
      headers: headers(),
    });
    expect(res.status()).toBe(200);
  });

  test('查询订单列表 - 按员工筛选', async ({ request }) => {
    const res = await request.get(`${BASE}/nail/pos/orders?page=1&page_size=20&employee_id=${EMPLOYEE_ID}`, {
      headers: headers(),
    });
    expect(res.status()).toBe(200);
  });

  test('查询订单列表 - 按状态筛选（completed）', async ({ request }) => {
    const res = await request.get(`${BASE}/nail/pos/orders?page=1&page_size=20&status=completed`, {
      headers: headers(),
    });
    expect(res.status()).toBe(200);
  });

  test('查询订单列表 - 按状态筛选（pending）', async ({ request }) => {
    const res = await request.get(`${BASE}/nail/pos/orders?page=1&page_size=20&status=pending`, {
      headers: headers(),
    });
    expect(res.status()).toBe(200);
  });

  test('查询订单列表 - 按状态筛选（voided）', async ({ request }) => {
    const res = await request.get(`${BASE}/nail/pos/orders?page=1&page_size=20&status=voided`, {
      headers: headers(),
    });
    expect(res.status()).toBe(200);
  });

  test('今日统计 - summary', async ({ request }) => {
    const res = await request.get(`${BASE}/nail/pos/summary?period=day`, {
      headers: headers(),
    });
    expect(res.status()).toBe(200);
    const data = await res.json();
    expect(data).toHaveProperty('total_revenue_cents');
    expect(data).toHaveProperty('transaction_count');
  });

  test('挂单列表', async ({ request }) => {
    const res = await request.get(`${BASE}/nail/pos/holds`, {
      headers: headers(),
    });
    expect(res.status()).toBe(200);
  });
});

// ═══════════════════════════════════════════════════
// 八、预约流程
// ═══════════════════════════════════════════════════
test.describe('预约流程', () => {

  test('创建预约 - 完整字段', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/appointments`, {
      headers: headers(),
      data: {
        client_id: ADA_ID,
        service_id: SVC_PURE,
        employee_id: EMPLOYEE_ID,
        start_time: '2026-07-27T10:00:00Z',
        source: 'sys_employee',
        items: [{ service_id: SVC_PURE, employee_id: EMPLOYEE_ID, start_time: '2026-07-27T10:00:00Z', price_cents: 8800 }],
        notes: 'E2E完整字段测试',
      },
    });
    expect(res.status()).toBe(201);
    const data = await res.json();
    expect(data.status).toBe('confirmed');
    expect(data.id).toBeTruthy();
  });

  test('创建预约 - 前端格式（缺顶层字段）不应 500', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/appointments`, {
      headers: headers(),
      data: {
        items: [{ service_id: SVC_PURE, employee_id: null, start_time: '2026-07-27T14:00:00Z' }],
        notes: '',
        client_id: ADA_ID,
      },
    });
    expect(res.status()).not.toBe(500);
  });

  test('创建预约 - 多服务项', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/appointments`, {
      headers: headers(),
      data: {
        client_id: ADA_ID,
        service_id: SVC_PURE,
        employee_id: EMPLOYEE_ID,
        start_time: '2026-07-27T16:00:00Z',
        source: 'sys_employee',
        items: [
          { service_id: SVC_PURE, employee_id: EMPLOYEE_ID, start_time: '2026-07-27T16:00:00Z', price_cents: 8800 },
          { service_id: SVC_GRADIENT, employee_id: EMPLOYEE_ID, start_time: '2026-07-27T17:00:00Z', price_cents: 13800 },
        ],
        notes: '多服务预约',
      },
    });
    expect(res.status()).toBe(201);
  });

  test('创建预约 - 无效 service_id 不应 500', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/appointments`, {
      headers: headers(),
      data: {
        client_id: ADA_ID,
        service_id: 'invalid_service',
        employee_id: EMPLOYEE_ID,
        start_time: '2026-07-27T18:00:00Z',
        source: 'sys_employee',
        items: [{ service_id: 'invalid_service', employee_id: EMPLOYEE_ID, start_time: '2026-07-27T18:00:00Z', price_cents: 8800 }],
        notes: '',
      },
    });
    expect(res.status()).not.toBe(500);
  });

  test('创建预约 - 过去时间应报错', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/appointments`, {
      headers: headers(),
      data: {
        client_id: ADA_ID,
        service_id: SVC_PURE,
        employee_id: EMPLOYEE_ID,
        start_time: '2020-01-01T10:00:00Z',
        source: 'sys_employee',
        items: [{ service_id: SVC_PURE, employee_id: EMPLOYEE_ID, start_time: '2020-01-01T10:00:00Z', price_cents: 8800 }],
        notes: '',
      },
    });
    // 过去时间应该报错或至少不 500
    expect(res.status()).not.toBe(500);
  });

  test('查询预约列表', async ({ request }) => {
    const res = await request.get(`${BASE}/nail/appointments?start=2026-07-27&end=2026-07-27`, {
      headers: headers(),
    });
    expect(res.status()).toBe(200);
    const data = await res.json();
    expect(data).toHaveProperty('appointments');
  });
});

// ═══════════════════════════════════════════════════
// 九、安全与边界
// ═══════════════════════════════════════════════════
test.describe('安全与边界', () => {

  test('无 token 访问应返回 401', async ({ request }) => {
    const res = await request.get(`${BASE}/nail/pos/orders`, {
      headers: { 'Product': 'inail' },
    });
    expect(res.status()).toBe(401);
  });

  test('过期 token 应返回 401', async ({ request }) => {
    const res = await request.get(`${BASE}/nail/pos/orders`, {
      headers: { 'Product': 'inail', 'Authorization': 'Bearer expired_token_123' },
    });
    expect(res.status()).toBe(401);
  });

  test('负数金额应报错', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        items: [serviceItem(SVC_PURE, '纯色美甲', -8800)],
        split_payments: [{ method: 'cash', amount_cents: -8800 }],
      }),
    });
    expect(res.status()).not.toBe(201);
    expect(res.status()).not.toBe(500);
  });

  test('超大金额不应崩溃', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        items: [serviceItem(SVC_PURE, '纯色美甲', 99999999)],
        split_payments: [{ method: 'cash', amount_cents: 99999999 }],
      }),
    });
    expect(res.status()).not.toBe(500);
  });

  test('无效支付方式应报错', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        method: 'bitcoin',
        split_payments: [{ method: 'bitcoin', amount_cents: 8800 }],
      }),
    });
    expect(res.status()).not.toBe(500);
    expect(res.status()).toBeGreaterThanOrEqual(400);
  });

  test('split_payments 金额总和不等于 items 总和应报错', async ({ request }) => {
    const res = await request.post(`${BASE}/nail/pos/transactions`, {
      headers: headers(),
      data: makeTransaction({
        items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
        split_payments: [{ method: 'cash', amount_cents: 5000 }], // 少付了
      }),
    });
    expect(res.status()).not.toBe(500);
  });
});
