import { INestApplication } from "@nestjs/common";
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 { truncateAll } from "./helpers/db";
import { seedMerchantWithApiKey, type SeededMerchant } from "./helpers/seed";
import { buildTestApp, type TestAppHandle } from "./helpers/test-app";

async function createPendingIntent(
  app: INestApplication,
  bearer: string,
): Promise<{ intentId: string; providerPaymentId: string }> {
  const created = await request(app.getHttpServer())
    .post("/payment-intents")
    .set("Authorization", `Bearer ${bearer}`)
    .send({ amount: 100000, currency: "PYG", country: "PY" })
    .expect(201);
  const confirmed = await request(app.getHttpServer())
    .post(`/payment-intents/${created.body.id}/confirm`)
    .set("Authorization", `Bearer ${bearer}`)
    .send({})
    .expect(201);
  return {
    intentId: created.body.id,
    providerPaymentId: confirmed.body.attempt.providerPaymentId,
  };
}

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

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

  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);
    handle.fakeRouter.program({ createStatus: "pending" });
  });

  it("valid signature transitions intent from pending → approved", async () => {
    const { intentId, providerPaymentId } = await createPendingIntent(app, seeded.apiKey);

    const body = { id: providerPaymentId, status: "approved" };
    const res = await request(app.getHttpServer())
      .post(`/webhooks/in/dlocal/${seeded.providerConfigId}`)
      .set("x-fake-signature", "valid")
      .set("Content-Type", "application/json")
      .send(body)
      .expect(200);

    expect(res.body.received).toBe(true);
    expect(res.body.eventId).toMatch(/^wei_/);

    const intent = await prisma.paymentIntent.findUniqueOrThrow({ where: { id: intentId } });
    expect(intent.status).toBe("approved");

    const eventIn = await prisma.webhookEventIn.findFirst({
      where: { id: res.body.eventId },
    });
    expect(eventIn?.processed).toBe(true);
    expect(eventIn?.error).toBeNull();
  });

  it("invalid signature returns 401 and persists with error for traceability", async () => {
    await createPendingIntent(app, seeded.apiKey);

    await request(app.getHttpServer())
      .post(`/webhooks/in/dlocal/${seeded.providerConfigId}`)
      .set("Content-Type", "application/json")
      .send({ id: "anything", status: "approved" })
      .expect(401);

    const events = await prisma.webhookEventIn.findMany();
    expect(events).toHaveLength(1);
    expect(events[0].processed).toBe(false);
    expect(events[0].error).toMatch(/invalid signature/i);
  });

  it("dedup: same signature for the same attempt is marked deduped on the second receive", async () => {
    const { providerPaymentId } = await createPendingIntent(app, seeded.apiKey);
    const body = { id: providerPaymentId, status: "approved" };

    const first = await request(app.getHttpServer())
      .post(`/webhooks/in/dlocal/${seeded.providerConfigId}`)
      .set("x-fake-signature", "valid")
      .set("x-dlocalgo-signature", "shared-sig-abc")
      .set("Content-Type", "application/json")
      .send(body)
      .expect(200);
    expect(first.body.deduped).toBeFalsy();

    const second = await request(app.getHttpServer())
      .post(`/webhooks/in/dlocal/${seeded.providerConfigId}`)
      .set("x-fake-signature", "valid")
      .set("x-dlocalgo-signature", "shared-sig-abc")
      .set("Content-Type", "application/json")
      .send(body)
      .expect(200);
    expect(second.body.deduped).toBe(true);
  });

  it("unknown providerPaymentId is persisted but returns 200 (don't make provider retry)", async () => {
    await createPendingIntent(app, seeded.apiKey);

    const res = await request(app.getHttpServer())
      .post(`/webhooks/in/dlocal/${seeded.providerConfigId}`)
      .set("x-fake-signature", "valid")
      .set("Content-Type", "application/json")
      .send({ id: "fake_pay_does_not_exist", status: "approved" })
      .expect(200);

    const event = await prisma.webhookEventIn.findFirstOrThrow({
      where: { id: res.body.eventId },
    });
    expect(event.processed).toBe(true);
    expect(event.attemptId).toBeNull();
    expect(event.error).toMatch(/no attempt/i);
  });

  it("400 when providerConfigId does not exist", async () => {
    await request(app.getHttpServer())
      .post(`/webhooks/in/dlocal/pcfg_does_not_exist`)
      .set("x-fake-signature", "valid")
      .set("Content-Type", "application/json")
      .send({ id: "x", status: "approved" })
      .expect(400);
  });
});
