/**
 * L1 集成测试 — site-attendance v1.5 共享签到核心信任边界
 *
 * 覆盖 docs/modules/site-attendance/09-test-scenarios.md 中 8 个核心场景：
 *   SCN-SA-V15-002 SIGNED 无 token 拒绝
 *   SCN-SA-V15-003 SIGNED 有效 QR token 通过
 *   SCN-SA-V15-004 QR token 过期被拒
 *   SCN-SA-V15-006 QR token 签名伪造被拒
 *   SCN-SA-V15-010 Dispatch 签发 ticket（本地跳）
 *   SCN-SA-V15-013 Ticket 重放被拒
 *   SCN-SA-V15-017 Partner 保存 hostname 白名单
 *   SCN-SA-V15-020 启用共享签到时缺 companyId 校验
 *
 * 剩余 17 个场景通过 L2 E2E（Playwright MCP）覆盖（见 10-e2e-test-spec.md）
 */

import { INestApplication } from '@nestjs/common';
import request from 'supertest';
import { randomBytes } from 'crypto';
import { PrismaService } from '@/core/database/prisma/prisma.service';
import { createTestApp } from '../../helpers/app.helper';
import { cleanupByPrefix } from '../../helpers/cleanup.helper';
import { createAdminUser } from '../../helpers/factories/user.factory';
import {
  issueQrToken,
  issueTicket,
} from '@/modules/site-attendance/utils/shared-checkin.util';

// 注入 env 前必须在导入 util 之前设置
const TEST_SECRET = randomBytes(32).toString('hex');
const TEST_ALLOWED_HOST = 'ff.test.example.com';

describe('Site Attendance v1.5 Shared Checkin - L1', () => {
  let app: INestApplication;
  let prisma: PrismaService;
  let adminToken: string;
  let adminUserId: string;

  beforeAll(async () => {
    process.env.NODE_ENV = 'test';
    process.env.SHARED_CHECKIN_SECRET = TEST_SECRET;
    process.env.SHARED_CHECKIN_ALLOWED_HOSTS = `${TEST_ALLOWED_HOST},aixc.test.example.com`;
    app = await createTestApp();
    prisma = app.get<PrismaService>(PrismaService);
  });

  beforeEach(async () => {
    const suffix = `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
    const admin = await createAdminUser({
      username: `t_sc_admin_${suffix}`,
      email: `t_sc_admin_${suffix}@example.com`,
      password: 'Admin@123',
      displayName: 't_Shared Checkin Admin',
    });
    adminUserId = admin.id;
    const login = await request(app.getHttpServer())
      .post('/api/v1/auth/login')
      .send({ username: admin.username, password: 'Admin@123' })
      .expect(200);
    adminToken = login.body.data.accessToken as string;
  });

  afterEach(async () => {
    jest.restoreAllMocks();
    // issue #165 PR b: 切到前缀过滤 cleanup（schema 无关，零维护负担）
    await cleanupByPrefix(prisma);
  });

  afterAll(async () => {
    await app.close();
  });

  // ========== 辅助 ==========

  async function createSignedCheckpoint(overrides: Partial<Record<string, any>> = {}) {
    const name = `t_CP-${Math.random().toString(36).slice(2, 8)}`;
    const res = await request(app.getHttpServer())
      .post('/api/v1/site-attendance/checkpoints')
      .set('Authorization', `Bearer ${adminToken}`)
      .send({
        name,
        timezone: 'America/Los_Angeles',
        latitude: 34.05,
        longitude: -118.24,
        accessMode: 'SIGNED',
        qrRotationSeconds: 3600,
        qrGraceSeconds: 120,
        ...overrides,
      })
      .expect(201);
    return res.body.data;
  }

  async function createPublicCheckpoint() {
    const name = `CP-PUB-${Math.random().toString(36).slice(2, 8)}`;
    const res = await request(app.getHttpServer())
      .post('/api/v1/site-attendance/checkpoints')
      .set('Authorization', `Bearer ${adminToken}`)
      .send({
        name,
        timezone: 'America/Los_Angeles',
        latitude: 34.05,
        longitude: -118.24,
        accessMode: 'PUBLIC',
      })
      .expect(201);
    return res.body.data;
  }

  // ========== 测试 ==========

  it('[SCN-002] SIGNED checkpoint 不带 t 和 ticket 访问 /public 被拒 401', async () => {
    const cp = await createSignedCheckpoint();

    const res = await request(app.getHttpServer())
      .get(`/api/v1/site-attendance/checkpoints/code/${cp.code}/public`)
      .expect(401);

    expect(res.body.error?.code ?? res.body.code).toBe('QR_TOKEN_MISSING');
  });

  it('[SCN-003] SIGNED checkpoint 有效 QR token 可以访问 /public', async () => {
    const cp = await createSignedCheckpoint();
    const { token } = issueQrToken({
      scope: 'checkpoint',
      checkpointCode: cp.code,
      rotationSeconds: 3600,
    });

    const res = await request(app.getHttpServer())
      .get(`/api/v1/site-attendance/checkpoints/code/${cp.code}/public`)
      .query({ t: token })
      .expect(200);

    expect(res.body.data.accessMode).toBe('SIGNED');
    expect(res.body.data.sharedCheckinEnabled).toBe(false);
  });

  it('[SCN-004] 过期 QR token（超过宽限期）被拒 401 QR_TOKEN_EXPIRED', async () => {
    const cp = await createSignedCheckpoint();
    const nowMs = Date.now();
    const rotSec = 3600;
    const bucketNow = Math.floor(nowMs / (rotSec * 1000));
    // 构造两个 bucket 前的 token（彻底超出宽限期）
    const oldBucket = bucketNow - 2;
    const { token } = issueQrToken({
      scope: 'checkpoint',
      checkpointCode: cp.code,
      rotationSeconds: rotSec,
      now: oldBucket * rotSec * 1000,
    });

    const res = await request(app.getHttpServer())
      .get(`/api/v1/site-attendance/checkpoints/code/${cp.code}/public`)
      .query({ t: token })
      .expect(401);

    expect(res.body.error?.code ?? res.body.code).toBe('QR_TOKEN_EXPIRED');
  });

  it('[SCN-006] 伪造签名的 QR token 被拒 401 QR_TOKEN_INVALID', async () => {
    const cp = await createSignedCheckpoint();
    const nowMs = Date.now();
    const bucket = Math.floor(nowMs / (3600 * 1000));
    const fakeToken = `${bucket}.deadbeef${randomBytes(28).toString('hex')}`;

    const res = await request(app.getHttpServer())
      .get(`/api/v1/site-attendance/checkpoints/code/${cp.code}/public`)
      .query({ t: fakeToken })
      .expect(401);

    expect(res.body.error?.code ?? res.body.code).toBe('QR_TOKEN_INVALID');
  });

  it('[SCN-010] Dispatch（本地跳）签发 ticket，URL 含 ticket 参数', async () => {
    const cp = await createSignedCheckpoint({
      sharedCheckinEnabled: true,
      sharedCompanyId: 'ff',
      sharedCompanyLabel: 'Faraday Future',
    });
    const { token: qrToken } = issueQrToken({
      scope: 'shared',
      checkpointCode: cp.code,
      rotationSeconds: 3600,
    });

    const res = await request(app.getHttpServer())
      .post('/api/v1/site-attendance/shared-checkin/dispatch')
      .set('Referer', `https://${TEST_ALLOWED_HOST}/siteattendance/shared/${cp.code}`)
      .send({
        checkpointCode: cp.code,
        qrToken,
        choice: { companyId: 'ff' },
      })
      .expect(201);

    expect(res.body.data.targetMode).toBe('local');
    expect(res.body.data.redirectUrl).toContain(`/siteattendance/c/${cp.code}?ticket=`);
    expect(res.body.data.ticketExpiresAt).toBeGreaterThan(Date.now());
  });

  it('[SCN-013] Ticket 重放被拒 409 TICKET_ALREADY_USED', async () => {
    const cp = await createSignedCheckpoint({
      sharedCheckinEnabled: true,
      sharedCompanyId: 'ff',
      sharedCompanyLabel: 'Faraday Future',
    });
    const { ticket } = issueTicket({
      companyId: 'ff',
      targetMode: 'local',
      targetCode: cp.code,
      targetUrl: null,
      dispatchCheckpointId: cp.id,
      dispatchOrigin: `https://${TEST_ALLOWED_HOST}/siteattendance/shared/${cp.code}`,
    });

    // 首次校验通过
    await request(app.getHttpServer())
      .post('/api/v1/site-attendance/shared-checkin/validate-ticket')
      .send({ ticket, targetCheckpointCode: cp.code })
      .expect(201);

    // 再次校验 → 409
    const res2 = await request(app.getHttpServer())
      .post('/api/v1/site-attendance/shared-checkin/validate-ticket')
      .send({ ticket, targetCheckpointCode: cp.code })
      .expect(409);
    expect(res2.body.error?.code ?? res2.body.code).toBe('TICKET_ALREADY_USED');
  });

  it('[SCN-017] Partner targetUrl hostname 不在白名单时保存被拒 400', async () => {
    const cp = await createSignedCheckpoint({
      sharedCheckinEnabled: true,
      sharedCompanyId: 'ff',
      sharedCompanyLabel: 'Faraday Future',
    });

    const res = await request(app.getHttpServer())
      .post(`/api/v1/site-attendance/checkpoints/${cp.id}/partners`)
      .set('Authorization', `Bearer ${adminToken}`)
      .send({
        companyId: 'aixc',
        companyLabel: 'AIxCrypto',
        targetUrl: 'https://evil.example.com/siteattendance/c/AIXC-HQ',
      })
      .expect(400);

    expect(res.body.error?.code ?? res.body.code).toBe(
      'PARTNER_TARGETURL_HOST_NOT_ALLOWED',
    );
  });

  it('[SCN-020] sharedCheckinEnabled=true 时缺 companyId 被拒 400', async () => {
    const res = await request(app.getHttpServer())
      .post('/api/v1/site-attendance/checkpoints')
      .set('Authorization', `Bearer ${adminToken}`)
      .send({
        name: 'CP-TEST',
        timezone: 'America/Los_Angeles',
        latitude: 34.05,
        longitude: -118.24,
        accessMode: 'SIGNED',
        qrRotationSeconds: 3600,
        sharedCheckinEnabled: true,
        // 故意缺 sharedCompanyId / sharedCompanyLabel
      })
      .expect(400);

    expect(res.body.error?.code ?? res.body.code).toBe(
      'CHECKPOINT_SHARED_COMPANY_MISSING',
    );
  });
});
