import { describe, it, expect, afterEach } from "vitest";
import prisma from "../config/database";
import {
  generateIssuedOrderNumber,
  previewIssuedOrderNumber,
  releaseIssuedOrderNumber,
} from "../services/numbering.service";
import { CreateIssuedOrderSchema } from "../schemas/issued-orders.schema";
import {
  computeIssuedOrderTotals,
  createIssuedOrder,
  getIssuedOrder,
  updateIssuedOrder,
  deleteIssuedOrder,
} from "../services/issued-orders.service";

afterEach(async () => {
  await prisma.number_sequences.deleteMany({ where: { type: "issued_order" } });
});

describe("issued-order numbering", () => {
  it("generates sequential numbers", async () => {
    const year = new Date().getFullYear();
    const a = await generateIssuedOrderNumber(year);
    const b = await generateIssuedOrderNumber(year);
    expect(Number(b.number)).toBe(Number(a.number) + 1);
  });

  it("preview does not consume the sequence", async () => {
    const year = new Date().getFullYear();
    const p1 = await previewIssuedOrderNumber(year);
    const p2 = await previewIssuedOrderNumber(year);
    expect(p1.number).toBe(p2.number);
  });

  it("release frees only the highest number", async () => {
    const year = new Date().getFullYear();
    await generateIssuedOrderNumber(year); // seq 1
    await generateIssuedOrderNumber(year); // seq 2
    const c = await generateIssuedOrderNumber(year); // seq 3 (current highest)
    const beforeRelease = (await previewIssuedOrderNumber(year)).number; // seq 4
    // Releasing a number that is NOT the current highest is a no-op.
    await releaseIssuedOrderNumber(year, "00000000");
    expect((await previewIssuedOrderNumber(year)).number).toBe(beforeRelease);
    // Releasing the current highest reclaims it.
    await releaseIssuedOrderNumber(year, c.number);
    expect((await previewIssuedOrderNumber(year)).number).toBe(c.number);
  });
});

describe("CreateIssuedOrderSchema", () => {
  it("coerces string form numbers and rejects an out-of-range VAT", () => {
    const ok = CreateIssuedOrderSchema.safeParse({
      customer_id: "5",
      vat_rate: "21",
      items: [{ description: "X", quantity: "2", unit_price: "100" }],
    });
    expect(ok.success).toBe(true);
    if (ok.success) expect(ok.data.customer_id).toBe(5);

    const bad = CreateIssuedOrderSchema.safeParse({ vat_rate: "200" });
    expect(bad.success).toBe(false);
  });
});

const createdIds: number[] = [];
const createdCustomerIds: number[] = [];

afterEach(async () => {
  for (const id of createdIds)
    await prisma.issued_orders.deleteMany({ where: { id } });
  for (const id of createdCustomerIds)
    await prisma.customers.deleteMany({ where: { id } });
  createdIds.length = 0;
  createdCustomerIds.length = 0;
});

async function makeCustomer() {
  const c = await prisma.customers.create({
    data: { name: "Dodavatel s.r.o." },
  });
  createdCustomerIds.push(c.id);
  return c;
}

describe("computeIssuedOrderTotals (NET + VAT-on-top)", () => {
  it("adds VAT on top of net, rounded per line", () => {
    const t = computeIssuedOrderTotals(
      [
        { quantity: 2, unit_price: 100, vat_rate: 21 },
        { quantity: 1, unit_price: 50, vat_rate: 12 },
      ],
      true,
      21,
    );
    expect(t.subtotal).toBe(250);
    expect(t.vat_amount).toBe(48);
    expect(t.total).toBe(298);
  });

  it("zeroes VAT when apply_vat is false but keeps the net subtotal", () => {
    const t = computeIssuedOrderTotals(
      [{ quantity: 3, unit_price: 100, vat_rate: 21 }],
      false,
      21,
    );
    expect(t).toEqual({ subtotal: 300, vat_amount: 0, total: 300 });
  });

  it("falls back to the document rate when a line rate is null", () => {
    const t = computeIssuedOrderTotals(
      [{ quantity: 1, unit_price: 100, vat_rate: null }],
      true,
      15,
    );
    expect(t.vat_amount).toBe(15);
  });
});

describe("createIssuedOrder", () => {
  it("assigns a PO number, defaults to draft, stores items", async () => {
    const c = await makeCustomer();
    const order = await createIssuedOrder({
      customer_id: c.id,
      items: [
        { description: "Materiál", quantity: 2, unit_price: 100, vat_rate: 21 },
      ],
    });
    createdIds.push(order.id);
    expect(order.po_number).toBeTruthy();
    expect(order.status).toBe("draft");
    const items = await prisma.issued_order_items.findMany({
      where: { issued_order_id: order.id },
    });
    expect(items.length).toBe(1);
    expect(Number(items[0].unit_price)).toBe(100);
  });
});

describe("updateIssuedOrder status transitions", () => {
  it("allows draft -> sent and rejects draft -> completed", async () => {
    const order = await createIssuedOrder({});
    createdIds.push(order.id);
    const ok = await updateIssuedOrder(order.id, { status: "sent" });
    expect("error" in ok).toBe(false);
    const bad = await updateIssuedOrder(order.id, { status: "completed" });
    expect("error" in bad && bad.error).toBe("invalid_transition");
  });

  it("locks items once confirmed", async () => {
    const order = await createIssuedOrder({
      items: [{ description: "A", quantity: 1, unit_price: 10 }],
    });
    createdIds.push(order.id);
    await updateIssuedOrder(order.id, { status: "sent" });
    await updateIssuedOrder(order.id, { status: "confirmed" });
    await updateIssuedOrder(order.id, {
      items: [{ description: "B", quantity: 9, unit_price: 99 }],
    });
    const items = await prisma.issued_order_items.findMany({
      where: { issued_order_id: order.id },
    });
    expect(items.length).toBe(1);
    expect(items[0].description).toBe("A");
  });
});

describe("getIssuedOrder", () => {
  it("returns customer_name and valid_transitions", async () => {
    const c = await makeCustomer();
    const order = await createIssuedOrder({ customer_id: c.id });
    createdIds.push(order.id);
    const detail = await getIssuedOrder(order.id);
    expect(detail?.customer_name).toBe("Dodavatel s.r.o.");
    expect(detail?.valid_transitions).toContain("sent");
  });
});

describe("deleteIssuedOrder", () => {
  it("deletes, cascades items, frees the latest number", async () => {
    const order = await createIssuedOrder({
      items: [{ description: "X", quantity: 1, unit_price: 1 }],
    });
    const before = order.po_number;
    await deleteIssuedOrder(order.id);
    expect(
      await prisma.issued_orders.findUnique({ where: { id: order.id } }),
    ).toBeNull();
    expect(
      (
        await prisma.issued_order_items.findMany({
          where: { issued_order_id: order.id },
        })
      ).length,
    ).toBe(0);
    expect((await previewIssuedOrderNumber()).number).toBe(before);
  });

  it("releases the number against the order's creation year, not the current year", async () => {
    const priorYear = new Date().getFullYear() - 1;
    // Allocate a real prior-year number (consumes that year's sequence: 0 -> 1)
    // so the PO number matches the prior year's current highest.
    const { number: poNumber } = await generateIssuedOrderNumber(priorYear);
    const order = await createIssuedOrder({ po_number: poNumber });
    createdIds.push(order.id);
    // Backdate creation into the prior year so delete derives that year.
    await prisma.issued_orders.update({
      where: { id: order.id },
      data: { created_at: new Date(priorYear, 5, 1, 12, 0, 0) },
    });
    await deleteIssuedOrder(order.id);
    // The prior-year sequence must be decremented (1 -> 0). With the old
    // current-year bug it would stay at 1 (no current-year row to match).
    const seq = await prisma.number_sequences.findFirst({
      where: { type: "issued_order", year: priorYear },
    });
    expect(seq?.last_number).toBe(0);
  });
});
