/**
 * L1 集成测试 — form-management webhook
 *
 * 锁定：
 *   - Webhook CRUD（API 路径 + 字段映射）
 *   - sendTestEvent → 写 log + success 字段反映 HTTP 结果
 *   - triggerEvent 成功路径：日志 retryCount=0
 *   - triggerEvent 失败路径：scheduleRetries 进行重试，retryCount 递增（M9 当前实现）
 *
 * 注：当前实现是进程内 setTimeout fire-and-forget，bullmq 接入是 Bucket D；
 * 本测试只锁定当前行为，不预设未来队列实现。
 */

import http from 'node:http';
import { AddressInfo } from 'node:net';
import { INestApplication } from '@nestjs/common';
import request from 'supertest';
import { PrismaService } from '@/core/database/prisma/prisma.service';
import { WebhookService } from '@/engines/form/form-management/services/webhook.service';
import { createTestApp } from '../../helpers/app.helper';
import { cleanupByPrefix } from '../../helpers/cleanup.helper';
import { createAdminUser } from '../../helpers/factories/user.factory';

interface ReceivingServer {
  url: string;
  hits: Array<{ headers: http.IncomingHttpHeaders; body: string }>;
  setStatus: (code: number) => void;
  close: () => Promise<void>;
}

async function startReceivingServer(): Promise<ReceivingServer> {
  const hits: ReceivingServer['hits'] = [];
  let nextStatus = 200;
  const server = http.createServer((req, res) => {
    const chunks: Buffer[] = [];
    req.on('data', (c) => chunks.push(c));
    req.on('end', () => {
      hits.push({ headers: req.headers, body: Buffer.concat(chunks).toString('utf8') });
      res.writeHead(nextStatus, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ ok: nextStatus < 400 }));
    });
  });
  await new Promise<void>((r) => server.listen(0, '127.0.0.1', r));
  const port = (server.address() as AddressInfo).port;
  return {
    url: `http://127.0.0.1:${port}/hook`,
    hits,
    setStatus: (c) => {
      nextStatus = c;
    },
    close: () =>
      new Promise<void>((r, j) => server.close((e) => (e ? j(e) : r()))),
  };
}

describe('form-management webhook - L1', () => {
  let app: INestApplication;
  let prisma: PrismaService;
  let webhookService: WebhookService;

  let token: string;
  let userId: string;

  beforeAll(async () => {
    process.env.NODE_ENV = 'test';
    app = await createTestApp();
    prisma = app.get<PrismaService>(PrismaService);
    webhookService = app.get<WebhookService>(WebhookService);
  });

  beforeEach(async () => {
    const suffix = `${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
    const user = await createAdminUser({
      username: `t_fmwh_${suffix}`,
      email: `t_fmwh_${suffix}@example.com`,
      password: 'Admin@123',
      displayName: 't_FmWh User',
    });
    userId = user.id;

    const login = await request(app.getHttpServer())
      .post('/api/v1/auth/login')
      .send({ username: user.username, password: 'Admin@123' })
      .expect(200);
    token = login.body.data.accessToken as string;
  });

  afterEach(async () => {
    await prisma.$transaction(async (tx) => {
      // FormWebhookLog onDelete: Cascade，所以删 webhook 即可
      await tx.formWebhook.deleteMany({ where: { name: { startsWith: 't_' } } });
    });
    await cleanupByPrefix(prisma);
  });

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

  // ============================================
  // 1. CRUD
  // ============================================
  describe('CRUD', () => {
    it('create → list 包含；get 返回详情；patch 改 enabled；delete 后查不到', async () => {
      const createRes = await request(app.getHttpServer())
        .post('/api/v1/form-management/webhooks')
        .set('Authorization', `Bearer ${token}`)
        .set('X-Region-Id', 'CN')
        .send({
          name: 't_wh_crud',
          description: 't_desc',
          url: 'https://example.com/none',
          events: ['form.submitted'],
        })
        .expect(201);
      const id = createRes.body.data.id as string;
      expect(createRes.body.data.enabled).toBe(true);
      expect(createRes.body.data.maxRetries).toBe(3);

      const listRes = await request(app.getHttpServer())
        .get('/api/v1/form-management/webhooks')
        .set('Authorization', `Bearer ${token}`)
        .set('X-Region-Id', 'CN')
        .expect(200);
      expect((listRes.body.data as any[]).find((w) => w.id === id)).toBeTruthy();

      const getRes = await request(app.getHttpServer())
        .get(`/api/v1/form-management/webhooks/${id}`)
        .set('Authorization', `Bearer ${token}`)
        .set('X-Region-Id', 'CN')
        .expect(200);
      expect(getRes.body.data.name).toBe('t_wh_crud');

      await request(app.getHttpServer())
        .patch(`/api/v1/form-management/webhooks/${id}`)
        .set('Authorization', `Bearer ${token}`)
        .set('X-Region-Id', 'CN')
        .send({ enabled: false })
        .expect(200);

      const after = await prisma.formWebhook.findUnique({ where: { id } });
      expect(after!.enabled).toBe(false);
      expect(after!.updatedBy).toBe(userId);

      await request(app.getHttpServer())
        .delete(`/api/v1/form-management/webhooks/${id}`)
        .set('Authorization', `Bearer ${token}`)
        .set('X-Region-Id', 'CN')
        .expect(200);

      const gone = await prisma.formWebhook.findUnique({ where: { id } });
      expect(gone).toBeNull();
    });
  });

  // ============================================
  // 2. sendTestEvent 写 log
  // ============================================
  describe('sendTestEvent', () => {
    it('对真实 HTTP 200 → log.success=true、有 statusCode/duration', async () => {
      const recv = await startReceivingServer();
      try {
        const wh = await prisma.formWebhook.create({
          data: {
            organizationId: null,
            name: 't_wh_test_event',
            url: recv.url,
            secret: 'secret',
            events: ['form.test'] as any,
            createdBy: userId,
          },
        });

        await request(app.getHttpServer())
          .post(`/api/v1/form-management/webhooks/${wh.id}/test`)
          .set('Authorization', `Bearer ${token}`)
          .set('X-Region-Id', 'CN')
          .send({})
          .expect(200);

        expect(recv.hits.length).toBe(1);
        const logs = await prisma.formWebhookLog.findMany({ where: { webhookId: wh.id } });
        expect(logs).toHaveLength(1);
        expect(logs[0].success).toBe(true);
        expect(logs[0].statusCode).toBe(200);
        expect(logs[0].duration).not.toBeNull();
        expect(logs[0].retryCount).toBe(0);
      } finally {
        await recv.close();
      }
    });
  });

  // ============================================
  // 3. triggerEvent 成功路径
  // ============================================
  describe('triggerEvent — 成功', () => {
    it('启用的 webhook 收到事件、log retryCount=0、不触发重试', async () => {
      const recv = await startReceivingServer();
      try {
        const wh = await prisma.formWebhook.create({
          data: {
            organizationId: null,
            name: 't_wh_trigger_ok',
            url: recv.url,
            secret: 'secret',
            events: ['form.instance.submitted'] as any,
            maxRetries: 2,
            createdBy: userId,
          },
        });

        await webhookService.triggerEvent(
          'form.instance.submitted',
          { foo: 'bar', ts: Date.now() },
          'CN' as any,
        );

        // 给 fire-and-forget 留点缓冲（成功不应触发重试）
        await new Promise((r) => setTimeout(r, 200));

        expect(recv.hits.length).toBe(1);
        const logs = await prisma.formWebhookLog.findMany({ where: { webhookId: wh.id } });
        expect(logs).toHaveLength(1);
        expect(logs[0].retryCount).toBe(0);
        expect(logs[0].success).toBe(true);

        // 签名头都在
        const headers = recv.hits[0].headers;
        expect(headers['x-webhook-event']).toBe('form.instance.submitted');
        expect(headers['x-webhook-signature']).toMatch(/^sha256=/);
      } finally {
        await recv.close();
      }
    });

    it('禁用的 webhook 不被触发', async () => {
      const recv = await startReceivingServer();
      try {
        const wh = await prisma.formWebhook.create({
          data: {
            organizationId: null,
            name: 't_wh_disabled',
            url: recv.url,
            secret: 'secret',
            events: ['form.instance.submitted'] as any,
            enabled: false,
            createdBy: userId,
          },
        });

        await webhookService.triggerEvent(
          'form.instance.submitted',
          {},
          'CN' as any,
        );
        await new Promise((r) => setTimeout(r, 100));

        expect(recv.hits.length).toBe(0);
        const logs = await prisma.formWebhookLog.findMany({ where: { webhookId: wh.id } });
        expect(logs).toHaveLength(0);
      } finally {
        await recv.close();
      }
    });
  });

  // ============================================
  // 4. triggerEvent 失败 → 重试链 retryCount 递增（M9）
  // ============================================
  describe('triggerEvent — 失败重试', () => {
    it('HTTP 500 + maxRetries=1 → 写两条日志，retryCount=0 + retryCount=1', async () => {
      const recv = await startReceivingServer();
      recv.setStatus(500);
      try {
        const wh = await prisma.formWebhook.create({
          data: {
            organizationId: null,
            name: 't_wh_retry',
            url: recv.url,
            secret: 'secret',
            events: ['form.instance.failed'] as any,
            maxRetries: 1,
            createdBy: userId,
          },
        });

        await webhookService.triggerEvent(
          'form.instance.failed',
          { ts: Date.now() },
          'CN' as any,
        );

        // 重试 backoff = 2^1 * 1000 = 2000ms，多留点 buffer
        // 轮询直到出现 retryCount=1 的日志或超时
        const deadline = Date.now() + 5000;
        let logs: any[] = [];
        while (Date.now() < deadline) {
          logs = await prisma.formWebhookLog.findMany({
            where: { webhookId: wh.id },
            orderBy: { retryCount: 'asc' },
          });
          if (logs.length >= 2) break;
          await new Promise((r) => setTimeout(r, 200));
        }

        expect(logs.length).toBeGreaterThanOrEqual(2);
        expect(logs[0].retryCount).toBe(0);
        expect(logs[0].success).toBe(false);
        expect(logs[0].statusCode).toBe(500);
        expect(logs[1].retryCount).toBe(1);
        expect(logs[1].success).toBe(false);
      } finally {
        await recv.close();
      }
    }, 15000);
  });
});
