// Integration tests for AR milestone closeout, decision 2 — the settable
// next-invoice-number API (server/src/settings.ts,
// db/migrations/0008_ar_closeout.sql's set_next_invoice_no()). Same
// createTestDb/buildApp/app.inject patterns as tests/server/ar-invoices.test.ts.

import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { createTestDb, type TestDb } from "../helpers/db.js";
// Where the database is, resolved once for the whole run: your ADMIN_URL /
// DATABASE_URL if you set one, otherwise the PostgreSQL Balanced Sum installed on
// this machine. Every file in this directory used to carry its own copy of that
// decision, hardcoded to the Docker container that no longer exists.
import { dbUrl } from "../helpers/test-db-admin.js";
import { buildApp } from "../../server/src/app.js";
import type { FastifyInstance } from "fastify";

async function buildTestApp(dbName: string): Promise<FastifyInstance> {
  return buildApp({
    databaseUrl: await dbUrl(dbName, "app", "app"),
    adminDatabaseUrl: await dbUrl(dbName),
  });
}

function post(app: FastifyInstance, companyId: string, url: string, payload?: unknown) {
  return app.inject({
    method: "POST", url, headers: { "x-company-id": companyId }, payload: payload ?? {},
  });
}
function put(app: FastifyInstance, companyId: string, url: string, payload?: unknown) {
  return app.inject({
    method: "PUT", url, headers: { "x-company-id": companyId }, payload: payload ?? {},
  });
}
function get(app: FastifyInstance, companyId: string, url: string) {
  return app.inject({ method: "GET", url, headers: { "x-company-id": companyId } });
}

let db: TestDb;
let app: FastifyInstance;

beforeAll(async () => {
  db = await createTestDb();
  app = await buildTestApp(db.dbName);
}, 60_000);

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

async function createCustomer(companyId: string, name: string): Promise<string> {
  const res = await post(app, companyId, "/api/c/customers", { name });
  expect(res.statusCode).toBe(201);
  return res.json().id;
}

async function issueInvoice(companyId: string, customerId: string, incomeAccountId: string): Promise<string> {
  const create = await post(app, companyId, "/api/c/invoices", {
    customerId,
    invoiceDate: "2024-06-01",
    lines: [{ unitPrice: "10.00", incomeAccountId }],
  });
  expect(create.statusCode).toBe(201);
  const invoiceId = create.json().invoiceId;
  const issue = await post(app, companyId, `/api/c/invoices/${invoiceId}/issue`);
  expect(issue.statusCode).toBe(200);
  return issue.json().invoiceNo;
}

describe("GET /api/c/settings/invoicing", () => {
  it("reports nextInvoiceNo 1 and highestUsed null for a company that has never issued an invoice", async () => {
    const res = await get(app, db.companyB, "/api/c/settings/invoicing");
    expect(res.statusCode).toBe(200);
    expect(res.json()).toEqual({ nextInvoiceNo: "1", highestUsed: null });
  });

  it("reflects the counter and the highest invoice_no once invoices have been issued", async () => {
    const id = await createCustomer(db.companyA, "Settings GET Co");
    const no = await issueInvoice(db.companyA, id, String(db.accounts["4000"]!.A));

    const res = await get(app, db.companyA, "/api/c/settings/invoicing");
    expect(res.statusCode).toBe(200);
    const body = res.json();
    expect(body.highestUsed).toBe(no);
    expect(Number(body.nextInvoiceNo)).toBe(Number(no) + 1);
  });
});

describe("PUT /api/c/settings/invoicing", () => {
  it("roundtrips: setting a fast-forward value is reflected by a subsequent GET", async () => {
    const id = await createCustomer(db.companyA, "Roundtrip Settings Co");
    await issueInvoice(db.companyA, id, String(db.accounts["4000"]!.A));

    const putRes = await put(app, db.companyA, "/api/c/settings/invoicing", { nextInvoiceNo: 500 });
    expect(putRes.statusCode).toBe(200);
    expect(putRes.json().nextInvoiceNo).toBe("500");

    const getRes = await get(app, db.companyA, "/api/c/settings/invoicing");
    expect(getRes.json().nextInvoiceNo).toBe("500");
  });

  it("422s in English when the value does not exceed the highest invoice number already used", async () => {
    const id = await createCustomer(db.companyA, "Too Low Settings Co");
    const no = await issueInvoice(db.companyA, id, String(db.accounts["4000"]!.A));

    const res = await put(app, db.companyA, "/api/c/settings/invoicing", { nextInvoiceNo: Number(no) });
    expect(res.statusCode).toBe(422);
    expect(res.json().error).toMatch(/greater than the highest one already used/i);
    expect(res.json().error).not.toMatch(/debit|credit/i);
  });

  it("400s in English for a non-numeric value", async () => {
    const res = await put(app, db.companyA, "/api/c/settings/invoicing", { nextInvoiceNo: "not-a-number" });
    expect(res.statusCode).toBe(400);
  });

  it("400s in English for a value less than 1", async () => {
    const res = await put(app, db.companyA, "/api/c/settings/invoicing", { nextInvoiceNo: 0 });
    expect(res.statusCode).toBe(400);
  });

  it("integration: setting the counter to 80 makes the NEXT issued invoice number 80", async () => {
    const id = await createCustomer(db.companyB, "Set 80 Co");
    const putRes = await put(app, db.companyB, "/api/c/settings/invoicing", { nextInvoiceNo: 80 });
    expect(putRes.statusCode).toBe(200);

    const no = await issueInvoice(db.companyB, id, String(db.accounts["4000"]!.B));
    expect(no).toBe("80");
  });

  it("company B's counter is unaffected by a PUT scoped to company A (RLS)", async () => {
    const putRes = await put(app, db.companyA, "/api/c/settings/invoicing", { nextInvoiceNo: 9000 });
    expect(putRes.statusCode).toBe(200);

    const getB = await get(app, db.companyB, "/api/c/settings/invoicing");
    expect(Number(getB.json().nextInvoiceNo)).toBeLessThan(9000);
  });
});
