import { INestApplication } from "@nestjs/common";
import { MerchantStatus, Provider, ProviderMode } from "@prisma/client";
import request from "supertest";
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { EncryptionService, EncryptedPayload } from "../src/common/crypto/encryption.service";
import { newId } from "../src/common/ids/id.util";
import { PrismaService } from "../src/prisma/prisma.service";
import { seedAdminUser, type SeededAdmin } from "./helpers/admin";
import { truncateAll } from "./helpers/db";
import { buildTestApp, type TestAppHandle } from "./helpers/test-app";

async function seedBareMerchant(prisma: PrismaService): Promise<string> {
  const m = await prisma.merchant.create({
    data: {
      id: newId("merchant"),
      name: "Bare Merchant",
      email: `bare-${Date.now()}@test.local`,
      slug: `bare-${Date.now()}`,
      defaultCountry: "PY",
      status: MerchantStatus.active,
    },
  });
  return m.id;
}

describe("Admin Provider Configs (e2e)", () => {
  let handle: TestAppHandle;
  let app: INestApplication;
  let prisma: PrismaService;
  let encryption: EncryptionService;
  let admin: SeededAdmin;
  let authHeader: { Authorization: string };
  let merchantId: string;

  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);
    admin = await seedAdminUser(prisma);
    authHeader = { Authorization: `Bearer ${admin.accessToken}` };
    merchantId = await seedBareMerchant(prisma);
  });

  it("requires admin auth", async () => {
    await request(app.getHttpServer())
      .get(`/admin/merchants/${merchantId}/provider-configs`)
      .expect(401);
  });

  it("creates, lists and retrieves a provider config without leaking credentials", async () => {
    const createRes = await request(app.getHttpServer())
      .post(`/admin/merchants/${merchantId}/provider-configs`)
      .set(authHeader)
      .send({
        provider: Provider.dlocal,
        mode: ProviderMode.sandbox,
        country: "PY",
        credentials: { apiKey: "k1", secretKey: "s1" },
        priority: 50,
      })
      .expect(201);

    expect(createRes.body).toMatchObject({
      merchantId,
      provider: "dlocal",
      mode: "sandbox",
      country: "PY",
      priority: 50,
      hasCredentials: true,
      active: true,
    });
    expect(createRes.body.credentials).toBeUndefined();
    expect(createRes.body.credentialsEncrypted).toBeUndefined();

    const listRes = await request(app.getHttpServer())
      .get(`/admin/merchants/${merchantId}/provider-configs`)
      .set(authHeader)
      .expect(200);

    expect(listRes.body.data).toHaveLength(1);
    expect(listRes.body.data[0].id).toBe(createRes.body.id);

    const getRes = await request(app.getHttpServer())
      .get(`/admin/merchants/${merchantId}/provider-configs/${createRes.body.id}`)
      .set(authHeader)
      .expect(200);
    expect(getRes.body.hasCredentials).toBe(true);

    // Credenciales realmente quedaron encriptadas en DB.
    const row = await prisma.merchantProviderConfig.findUnique({
      where: { id: createRes.body.id },
    });
    expect(row).not.toBeNull();
    const decrypted = encryption.decryptJson<Record<string, string>>(
      row!.credentialsEncrypted as unknown as EncryptedPayload,
    );
    expect(decrypted).toEqual({ apiKey: "k1", secretKey: "s1" });
  });

  it("rejects duplicate (provider, country, mode) with 409", async () => {
    const body = {
      provider: Provider.dlocal,
      mode: ProviderMode.sandbox,
      country: "PY",
      credentials: { apiKey: "k", secretKey: "s" },
    };
    await request(app.getHttpServer())
      .post(`/admin/merchants/${merchantId}/provider-configs`)
      .set(authHeader)
      .send(body)
      .expect(201);

    await request(app.getHttpServer())
      .post(`/admin/merchants/${merchantId}/provider-configs`)
      .set(authHeader)
      .send(body)
      .expect(409);
  });

  it("updates credentials (re-encrypts) and toggles active", async () => {
    const createRes = await request(app.getHttpServer())
      .post(`/admin/merchants/${merchantId}/provider-configs`)
      .set(authHeader)
      .send({
        provider: Provider.dlocal,
        mode: ProviderMode.sandbox,
        country: "PY",
        credentials: { apiKey: "old", secretKey: "old" },
      })
      .expect(201);

    const id = createRes.body.id;

    await request(app.getHttpServer())
      .patch(`/admin/merchants/${merchantId}/provider-configs/${id}`)
      .set(authHeader)
      .send({ credentials: { apiKey: "new", secretKey: "new" }, active: false })
      .expect(200);

    const after = await prisma.merchantProviderConfig.findUnique({ where: { id } });
    expect(after?.active).toBe(false);
    const decrypted = encryption.decryptJson<Record<string, string>>(
      after!.credentialsEncrypted as unknown as EncryptedPayload,
    );
    expect(decrypted).toEqual({ apiKey: "new", secretKey: "new" });
  });

  it("deletes a provider config", async () => {
    const createRes = await request(app.getHttpServer())
      .post(`/admin/merchants/${merchantId}/provider-configs`)
      .set(authHeader)
      .send({
        provider: Provider.dlocal,
        mode: ProviderMode.sandbox,
        country: "PY",
        credentials: { apiKey: "k", secretKey: "s" },
      })
      .expect(201);

    await request(app.getHttpServer())
      .delete(`/admin/merchants/${merchantId}/provider-configs/${createRes.body.id}`)
      .set(authHeader)
      .expect(200);

    const listRes = await request(app.getHttpServer())
      .get(`/admin/merchants/${merchantId}/provider-configs`)
      .set(authHeader)
      .expect(200);
    expect(listRes.body.data).toHaveLength(0);
  });

  it("404 on unknown id", async () => {
    await request(app.getHttpServer())
      .get(`/admin/merchants/${merchantId}/provider-configs/pcfg_nope`)
      .set(authHeader)
      .expect(404);
  });
});
