import { INestApplication } from '@nestjs/common';
import request from 'supertest';
import { PrismaService } from '@/core/database/prisma/prisma.service';
import { createTestApp } from '../../helpers/app.helper';
import { cleanupDatabase } from '../../helpers/cleanup.helper';
import { OutlookSyncService } from '@/modules/meeting-attendance/services/outlook-sync.service';

/**
 * Meeting-attendance · Outlook Webhook Controller L1 集成测试
 *
 * 覆盖 2 个 endpoint，均为 @Public（无需 Authorization header）：
 *   POST /meeting-attendance/integrations/outlook/webhooks/notifications
 *   POST /meeting-attendance/integrations/outlook/webhooks/lifecycle
 *
 * MS Graph 订阅验证流程（validationToken）：
 *   Graph 订阅时，微软先发带 validationToken 的 POST，
 *   服务端必须原文 echo 回去并以 text/plain 返回 200，
 *   否则 Graph 认为验证失败，不会建立订阅。
 *   本测试重点验证这条关键路径。
 *
 * 通知路径（body.value）：
 *   handleChangeNotifications / handleLifecycleNotifications 均通过
 *   jest.spyOn mock，不实际触发 DB 写入或 Graph 调用。
 *
 * 关联工单 #341 · Batch outlook-webhook
 */
describe('Meeting-attendance · Outlook Webhook API', () => {
  let app: INestApplication;
  let prisma: PrismaService;

  beforeAll(async () => {
    process.env.NODE_ENV = 'test';
    // GRAPH_NOTIFICATION_CLIENT_STATE 用于 validateNotificationClientState
    // 在测试环境固定一个值，方便构造合法 / 非法 clientState
    process.env.GRAPH_NOTIFICATION_CLIENT_STATE = 'test-client-state-secret';
    app = await createTestApp();
    prisma = app.get<PrismaService>(PrismaService);
  });

  afterEach(async () => {
    jest.restoreAllMocks();
    await cleanupDatabase(prisma);
  });

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

  // ============================================================
  // helpers
  // ============================================================

  function suffix() {
    return `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
  }

  const WEBHOOKS_BASE = '/api/v1/meeting-attendance/integrations/outlook/webhooks';

  /** spy handleChangeNotifications，返回 undefined（void） */
  function spyHandleChangeNotifications() {
    const svc = app.get(OutlookSyncService);
    return jest
      .spyOn(svc, 'handleChangeNotifications')
      .mockResolvedValue(undefined as any);
  }

  /** spy handleLifecycleNotifications，返回 undefined（void） */
  function spyHandleLifecycleNotifications() {
    const svc = app.get(OutlookSyncService);
    return jest
      .spyOn(svc, 'handleLifecycleNotifications')
      .mockResolvedValue(undefined as any);
  }

  // ============================================================
  // POST /notifications
  // ============================================================

  describe('POST /notifications', () => {
    // --- validationToken 路径 ---

    it('[1] 含 validationToken → 200 + text/plain + echo 原值', async () => {
      const token = 'ValidationToken_ABC123xyz';

      const resp = await request(app.getHttpServer())
        .post(`${WEBHOOKS_BASE}/notifications?validationToken=${encodeURIComponent(token)}`)
        .send({})
        .expect(200);

      expect(resp.headers['content-type']).toMatch(/text\/plain/i);
      expect(resp.text).toBe(token);
    });

    it('[2] validationToken 含 URL 编码字符 → 200 + decodeURIComponent 后的值', async () => {
      const rawToken = 'some token with spaces & special=chars';
      const encodedToken = encodeURIComponent(rawToken);

      const resp = await request(app.getHttpServer())
        .post(`${WEBHOOKS_BASE}/notifications?validationToken=${encodedToken}`)
        .send({})
        .expect(200);

      expect(resp.headers['content-type']).toMatch(/text\/plain/i);
      expect(resp.text).toBe(rawToken);
    });

    it('[3] @Public：validationToken 路径无需 Authorization header', async () => {
      const token = 'public-token-no-auth';

      const resp = await request(app.getHttpServer())
        .post(`${WEBHOOKS_BASE}/notifications?validationToken=${token}`)
        // 故意不带 Authorization
        .send({});

      expect(resp.status).not.toBe(401);
      expect(resp.status).toBe(200);
    });

    // --- 正常通知路径 ---

    it('[4] 含 body.value notifications（合法 clientState）→ 202 + accepted=true', async () => {
      const spy = spyHandleChangeNotifications();
      const s = suffix();

      const resp = await request(app.getHttpServer())
        .post(`${WEBHOOKS_BASE}/notifications`)
        .send({
          value: [
            {
              subscriptionId: `sub-${s}`,
              changeType: 'updated',
              clientState: 'test-client-state-secret',
              resource: 'users/test@example.com/events/evt-1',
            },
          ],
        })
        .expect(202);

      expect(resp.body.accepted).toBe(true);
      expect(resp.body.received).toBe(1);
      expect(spy).toHaveBeenCalledTimes(1);
      // 验证 spy 被传入正确的 notification 数组
      const callArg = spy.mock.calls[0][0];
      expect(Array.isArray(callArg)).toBe(true);
      expect(callArg[0].subscriptionId).toBe(`sub-${s}`);
    });

    it('[5] body.value 多条 notifications → 202 + received = n', async () => {
      spyHandleChangeNotifications();

      const resp = await request(app.getHttpServer())
        .post(`${WEBHOOKS_BASE}/notifications`)
        .send({
          value: [
            { subscriptionId: 'sub-a', changeType: 'updated', clientState: 'test-client-state-secret' },
            { subscriptionId: 'sub-b', changeType: 'created', clientState: 'test-client-state-secret' },
            { subscriptionId: 'sub-c', changeType: 'deleted', clientState: 'test-client-state-secret' },
          ],
        })
        .expect(202);

      expect(resp.body.received).toBe(3);
    });

    it('[6] body.value 为空数组 → 202 + received=0（不调 handleChangeNotifications 实际处理）', async () => {
      const spy = spyHandleChangeNotifications();

      const resp = await request(app.getHttpServer())
        .post(`${WEBHOOKS_BASE}/notifications`)
        .send({ value: [] })
        .expect(202);

      expect(resp.body.received).toBe(0);
      expect(resp.body.accepted).toBe(true);
      // service 仍被调用（空数组），循环内无迭代
      expect(spy).toHaveBeenCalledTimes(1);
    });

    it('[7] body 无 value 字段 → 202 + received=0（空数组兜底）', async () => {
      spyHandleChangeNotifications();

      const resp = await request(app.getHttpServer())
        .post(`${WEBHOOKS_BASE}/notifications`)
        .send({ somethingElse: 'data' })
        .expect(202);

      expect(resp.body.received).toBe(0);
    });

    it('[8] client-state 通过 header 传入 → 202（service 合并 header clientState）', async () => {
      const spy = spyHandleChangeNotifications();
      const s = suffix();

      // notification body 中无 clientState，走 header 注入
      const resp = await request(app.getHttpServer())
        .post(`${WEBHOOKS_BASE}/notifications`)
        .set('client-state', 'test-client-state-secret')
        .send({
          value: [
            {
              subscriptionId: `sub-header-${s}`,
              changeType: 'updated',
              // 没有 clientState 字段
            },
          ],
        })
        .expect(202);

      expect(resp.body.accepted).toBe(true);
      // 验证 header clientState 被合并进 notification
      const passedNotifications = spy.mock.calls[0][0];
      expect(passedNotifications[0].clientState).toBe('test-client-state-secret');
    });
  });

  // ============================================================
  // POST /lifecycle
  // ============================================================

  describe('POST /lifecycle', () => {
    // --- validationToken 路径 ---

    it('[9] 含 validationToken → 200 + text/plain + echo 原值', async () => {
      const token = 'LifecycleValidationToken_XYZ987';

      const resp = await request(app.getHttpServer())
        .post(`${WEBHOOKS_BASE}/lifecycle?validationToken=${encodeURIComponent(token)}`)
        .send({})
        .expect(200);

      expect(resp.headers['content-type']).toMatch(/text\/plain/i);
      expect(resp.text).toBe(token);
    });

    it('[10] @Public：lifecycle validationToken 无需 Authorization', async () => {
      const token = 'lifecycle-public-token';

      const resp = await request(app.getHttpServer())
        .post(`${WEBHOOKS_BASE}/lifecycle?validationToken=${token}`)
        .send({});

      expect(resp.status).not.toBe(401);
      expect(resp.status).toBe(200);
    });

    // --- lifecycle 通知路径 ---

    it('[11] 含 body.value lifecycle events → 202 + accepted=true', async () => {
      const spy = spyHandleLifecycleNotifications();
      const s = suffix();

      const resp = await request(app.getHttpServer())
        .post(`${WEBHOOKS_BASE}/lifecycle`)
        .send({
          value: [
            {
              subscriptionId: `sub-lifecycle-${s}`,
              lifecycleEvent: 'subscriptionRemoved',
            },
          ],
        })
        .expect(202);

      expect(resp.body.accepted).toBe(true);
      expect(resp.body.received).toBe(1);
      expect(spy).toHaveBeenCalledTimes(1);
      expect(spy.mock.calls[0][0]).toHaveLength(1);
    });

    it('[12] reauthorizationRequired event → 202（spy 验证 service 收到正确事件）', async () => {
      const spy = spyHandleLifecycleNotifications();
      const s = suffix();

      const resp = await request(app.getHttpServer())
        .post(`${WEBHOOKS_BASE}/lifecycle`)
        .send({
          value: [
            {
              subscriptionId: `sub-reauth-${s}`,
              lifecycleEvent: 'reauthorizationRequired',
            },
          ],
        })
        .expect(202);

      expect(resp.body.received).toBe(1);
      const passedEvents = spy.mock.calls[0][0];
      expect(passedEvents[0].lifecycleEvent).toBe('reauthorizationRequired');
    });

    it('[13] missed event → 202', async () => {
      spyHandleLifecycleNotifications();
      const s = suffix();

      const resp = await request(app.getHttpServer())
        .post(`${WEBHOOKS_BASE}/lifecycle`)
        .send({
          value: [
            {
              subscriptionId: `sub-missed-${s}`,
              lifecycleEvent: 'missed',
            },
          ],
        })
        .expect(202);

      expect(resp.body.accepted).toBe(true);
    });

    it('[14] body.value 为空数组 → 202 + received=0', async () => {
      const spy = spyHandleLifecycleNotifications();

      const resp = await request(app.getHttpServer())
        .post(`${WEBHOOKS_BASE}/lifecycle`)
        .send({ value: [] })
        .expect(202);

      expect(resp.body.received).toBe(0);
      expect(spy).toHaveBeenCalledTimes(1);
    });

    it('[15] 无 body.value 字段 → 202 + received=0', async () => {
      spyHandleLifecycleNotifications();

      const resp = await request(app.getHttpServer())
        .post(`${WEBHOOKS_BASE}/lifecycle`)
        .send({})
        .expect(202);

      expect(resp.body.received).toBe(0);
    });
  });
});
