import { INestApplication } from "@nestjs/common";
import { WebhookEventOutStatus } from "@prisma/client";
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
import request from "supertest";
import { EncryptionService } from "../src/common/crypto/encryption.service";
import { PrismaService } from "../src/prisma/prisma.service";
import { WebhookEndpointService } from "../src/webhook-endpoints/webhook-endpoint.service";
import { truncateAll } from "./helpers/db";
import { seedMerchantWithApiKey, type SeededMerchant } from "./helpers/seed";
import { buildTestApp, type TestAppHandle } from "./helpers/test-app";

describe("Webhook outbound emit (e2e)", () => {
  let handle: TestAppHandle;
  let app: INestApplication;
  let prisma: PrismaService;
  let encryption: EncryptionService;
  let endpoints: WebhookEndpointService;
  let seeded: SeededMerchant;

  beforeAll(async () => {
    handle = await buildTestApp();
    app = handle.app;
    prisma = app.get(PrismaService);
    encryption = app.get(EncryptionService);
    endpoints = app.get(WebhookEndpointService);
  });

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

  beforeEach(async () => {
    await truncateAll(prisma);
    handle.fakeRouter.reset();
    seeded = await seedMerchantWithApiKey(prisma, encryption);
    const cfg = await prisma.merchantProviderConfig.findUniqueOrThrow({
      where: { id: seeded.providerConfigId },
    });
    handle.fakeRouter.registerConfig(cfg);
  });

  it("does NOT persist event rows if no endpoint is subscribed", async () => {
    handle.fakeRouter.program({ createStatus: "approved" });
    const intent = await request(app.getHttpServer())
      .post("/payment-intents")
      .set("Authorization", `Bearer ${seeded.apiKey}`)
      .send({ amount: 50000, currency: "PYG", country: "PY" })
      .expect(201);

    await request(app.getHttpServer())
      .post(`/payment-intents/${intent.body.id}/confirm`)
      .set("Authorization", `Bearer ${seeded.apiKey}`)
      .send({})
      .expect(201);

    const count = await prisma.webhookEventOut.count();
    expect(count).toBe(0);
  });

  it("persists a payment_intent.succeeded event when an endpoint subscribed to it", async () => {
    const ep = await endpoints.create(seeded.merchantId, {
      url: "http://127.0.0.1:1/hook", // dirección no-routable: el worker fallará rápido sin afectar el test
      subscribedEvents: ["payment_intent.succeeded"],
    });

    handle.fakeRouter.program({ createStatus: "approved" });
    const intent = await request(app.getHttpServer())
      .post("/payment-intents")
      .set("Authorization", `Bearer ${seeded.apiKey}`)
      .send({ amount: 50000, currency: "PYG", country: "PY" })
      .expect(201);

    await request(app.getHttpServer())
      .post(`/payment-intents/${intent.body.id}/confirm`)
      .set("Authorization", `Bearer ${seeded.apiKey}`)
      .send({})
      .expect(201);

    const events = await prisma.webhookEventOut.findMany({
      where: { endpointId: ep.id },
    });
    expect(events).toHaveLength(1);
    const evt = events[0];
    expect(evt.eventType).toBe("payment_intent.succeeded");
    expect(evt.intentId).toBe(intent.body.id);
    expect([WebhookEventOutStatus.pending, WebhookEventOutStatus.failed]).toContain(evt.status);

    const payload = evt.payloadJson as Record<string, unknown>;
    expect(payload.event_type).toBe("payment_intent.succeeded");
    expect(payload.data).toMatchObject({
      payment_intent_id: intent.body.id,
      merchant_id: seeded.merchantId,
      status: "approved",
      currency: "PYG",
    });
  });

  it("'*' subscription receives every event", async () => {
    await endpoints.create(seeded.merchantId, {
      url: "http://127.0.0.1:1/hook",
      subscribedEvents: ["*"],
    });

    handle.fakeRouter.program({ createStatus: "approved" });
    const intent = await request(app.getHttpServer())
      .post("/payment-intents")
      .set("Authorization", `Bearer ${seeded.apiKey}`)
      .send({ amount: 50000, currency: "PYG", country: "PY" })
      .expect(201);

    await request(app.getHttpServer())
      .post(`/payment-intents/${intent.body.id}/confirm`)
      .set("Authorization", `Bearer ${seeded.apiKey}`)
      .send({})
      .expect(201);

    const types = (await prisma.webhookEventOut.findMany()).map((e) => e.eventType);
    expect(types).toContain("payment_intent.created");
    expect(types).toContain("payment_intent.succeeded");
  });

  it("inactive endpoints are skipped", async () => {
    const ep = await endpoints.create(seeded.merchantId, {
      url: "http://127.0.0.1:1/hook",
      subscribedEvents: ["payment_intent.succeeded"],
    });
    await endpoints.update(seeded.merchantId, ep.id, { active: false });

    handle.fakeRouter.program({ createStatus: "approved" });
    const intent = await request(app.getHttpServer())
      .post("/payment-intents")
      .set("Authorization", `Bearer ${seeded.apiKey}`)
      .send({ amount: 50000, currency: "PYG", country: "PY" })
      .expect(201);

    await request(app.getHttpServer())
      .post(`/payment-intents/${intent.body.id}/confirm`)
      .set("Authorization", `Bearer ${seeded.apiKey}`)
      .send({})
      .expect(201);

    const events = await prisma.webhookEventOut.count();
    expect(events).toBe(0);
  });
});
