import { describe, it, expect, beforeAll, afterAll } from "vitest";
import Fastify from "fastify";
import jwt from "jsonwebtoken";
import bankAccountsRoutes from "../routes/admin/bank-accounts";
import prisma from "../config/database";
import { config } from "../config/env";

// Fixture marker so cleanup never touches real data.
const N = "bank_accounts_test_";

let app: ReturnType<typeof Fastify>;
let token: string;
const createdIds: number[] = [];

beforeAll(async () => {
  app = Fastify({ logger: false });
  await app.register(bankAccountsRoutes, {
    prefix: "/api/admin/bank-accounts",
  });

  await prisma.users
    .deleteMany({ where: { username: { startsWith: N } } })
    .catch(() => {});

  // Admin role bypasses the settings.banking permission guard.
  const adminRole = await prisma.roles.findFirst({ where: { name: "admin" } });
  if (!adminRole)
    throw new Error("Test setup: no admin role found — seed the database");

  const user = await prisma.users.create({
    data: {
      username: `${N}user`,
      email: `${N}user@test.local`,
      password_hash: "x",
      first_name: "Bank",
      last_name: "Test",
      is_active: true,
      totp_enabled: false,
      role_id: adminRole.id,
    },
  });
  token = jwt.sign(
    { sub: user.id, username: user.username, role: "admin" },
    config.jwt.secret,
    { expiresIn: config.jwt.accessTokenExpiry },
  );
});

afterAll(async () => {
  if (createdIds.length) {
    await prisma.bank_accounts
      .deleteMany({ where: { id: { in: createdIds } } })
      .catch(() => {});
  }
  await prisma.users
    .deleteMany({ where: { username: { startsWith: N } } })
    .catch(() => {});
  await app.close();
});

const auth = { authorization: () => `Bearer ${token}` };

describe("bank accounts — flat-body create/update persists all fields", () => {
  it("creates an account from a flat top-level body and persists every field", async () => {
    const payload = {
      account_name: `${N}Hlavní účet`,
      bank_name: "Komerční banka",
      account_number: "123456789/0100",
      iban: "CZ6508000000192000145399",
      bic: "KOMBCZPP",
      currency: "CZK",
      is_default: true,
    };

    const post = await app.inject({
      method: "POST",
      url: "/api/admin/bank-accounts/",
      headers: { authorization: auth.authorization() },
      payload,
    });
    expect(post.statusCode).toBe(201);
    const id = post.json().data.id as number;
    expect(typeof id).toBe("number");
    createdIds.push(id);

    // Read back via the list endpoint and confirm NONE of the fields are blank.
    const row = await prisma.bank_accounts.findUnique({ where: { id } });
    expect(row?.account_name).toBe(payload.account_name);
    expect(row?.bank_name).toBe(payload.bank_name);
    expect(row?.account_number).toBe(payload.account_number);
    expect(row?.iban).toBe(payload.iban);
    expect(row?.bic).toBe(payload.bic);
    expect(row?.currency).toBe("CZK");
    expect(row?.is_default).toBe(true);
  });

  it("updates an account from a flat body that also carries the id", async () => {
    const created = await prisma.bank_accounts.create({
      data: { account_name: `${N}orig`, bank_name: "Orig", currency: "CZK" },
    });
    createdIds.push(created.id);

    // Mirrors the client payload: spread fields + id (id routes the URL/method,
    // is declared in the schema, and is ignored by the route which uses :id).
    const put = await app.inject({
      method: "PUT",
      url: `/api/admin/bank-accounts/${created.id}`,
      headers: { authorization: auth.authorization() },
      payload: {
        id: created.id,
        account_name: `${N}upraveno`,
        bank_name: "Nová banka",
        iban: "CZ9999",
        currency: "EUR",
        is_default: false,
      },
    });
    expect(put.statusCode).toBe(200);

    const row = await prisma.bank_accounts.findUnique({
      where: { id: created.id },
    });
    expect(row?.account_name).toBe(`${N}upraveno`);
    expect(row?.bank_name).toBe("Nová banka");
    expect(row?.iban).toBe("CZ9999");
    expect(row?.currency).toBe("EUR");
  });

  it("rejects the old nested {bank:{...}} body shape with 400 (no silent blank save)", async () => {
    const post = await app.inject({
      method: "POST",
      url: "/api/admin/bank-accounts/",
      headers: { authorization: auth.authorization() },
      payload: { bank: { account_name: `${N}nested`, bank_name: "X" } },
    });
    expect(post.statusCode).toBe(400);

    // And nothing was persisted under that name.
    const leaked = await prisma.bank_accounts.findFirst({
      where: { account_name: `${N}nested` },
    });
    expect(leaked).toBeNull();
  });

  it("requires authentication", async () => {
    const res = await app.inject({
      method: "GET",
      url: "/api/admin/bank-accounts/",
    });
    expect(res.statusCode).toBe(401);
  });
});
