import { describe, it, expect, afterEach } from "vitest";
import prisma from "../config/database";
import { createOrder, getOrderTotals } from "../services/orders.service";
import {
  createIssuedOrder,
  getIssuedOrderTotals,
} from "../services/issued-orders.service";

// Per-currency total aggregation for both order lists. These hit the real
// `app_test` DB via the service layer (suite convention) and prove the /stats
// endpoints sum order NET total per currency across the WHOLE filtered set
// (orders are not tax documents — no VAT anywhere), and that the where
// (month/year + status) is respected.
//
// A far-future month/year is used so seeded/other-test rows can't fall in the
// window and skew the deterministic sums (mirrors drafts-aggregation.test.ts).

const STATS_YEAR = 2097;
const STATS_MONTH = 8; // -> window [2097-08-01, 2097-09-01)

const createdOrderIds: number[] = [];
const createdIssuedIds: number[] = [];
const createdCustomerIds: number[] = [];

afterEach(async () => {
  for (const id of createdOrderIds)
    await prisma.orders.deleteMany({ where: { id } });
  for (const id of createdIssuedIds)
    await prisma.issued_orders.deleteMany({ where: { id } });
  for (const id of createdCustomerIds)
    await prisma.customers.deleteMany({ where: { id } });
  createdOrderIds.length = 0;
  createdIssuedIds.length = 0;
  createdCustomerIds.length = 0;
  // Finalized orders consume the shared/issued sequences — reset so we don't
  // leak a consumed number into sibling test files.
  await prisma.number_sequences.deleteMany({
    where: { type: { in: ["shared", "issued_order", "project"] } },
  });
});

function amountFor(
  totals: { currency: string; amount: number }[],
  currency: string,
): number | undefined {
  return totals.find((t) => t.currency === currency)?.amount;
}

describe("getOrderTotals (received orders) per-currency aggregation", () => {
  it("sums NET total per currency over the full filtered set", async () => {
    // Two CZK orders + one EUR, all in the same far-future month. NET only
    // (enrichOrder math — orders carry no VAT).
    //   CZK #1: 2 x 1000 = 2000
    //   CZK #2: 1 x 500  =  500   => CZK total 2500
    //   EUR   : 3 x 100  =  300   => EUR total  300
    const mk = async (currency: string, qty: number, price: number) => {
      const res = await createOrder({
        status: "prijata",
        currency,
        language: "cs",
        create_project: false,
        items: [{ description: "X", quantity: qty, unit_price: price }],
      });
      if (!("id" in res)) throw new Error(JSON.stringify(res));
      createdOrderIds.push(res.id!);
      // created_at is auto-managed; pin it into the stats window so the
      // month/year filter is deterministic.
      await prisma.orders.update({
        where: { id: res.id },
        data: {
          created_at: new Date(STATS_YEAR, STATS_MONTH - 1, 15, 12, 0, 0),
        },
      });
      return res.id!;
    };

    await mk("CZK", 2, 1000);
    await mk("CZK", 1, 500);
    await mk("EUR", 3, 100);

    const { totals } = await getOrderTotals({
      month: STATS_MONTH,
      year: STATS_YEAR,
    });
    expect(amountFor(totals, "CZK")).toBe(2500);
    expect(amountFor(totals, "EUR")).toBe(300);
  });

  it("respects the where: a different month and a status filter change the result", async () => {
    const mk = async (status: string, monthOffset: number) => {
      const res = await createOrder({
        status,
        currency: "CZK",
        language: "cs",
        create_project: false,
        items: [{ description: "X", quantity: 1, unit_price: 1000 }],
      });
      if (!("id" in res)) throw new Error(JSON.stringify(res));
      createdOrderIds.push(res.id!);
      await prisma.orders.update({
        where: { id: res.id },
        data: {
          created_at: new Date(
            STATS_YEAR,
            STATS_MONTH - 1 + monthOffset,
            15,
            12,
            0,
            0,
          ),
        },
      });
      return res.id!;
    };

    // One 'prijata' in the target month, one 'stornovana' in the target month,
    // one 'prijata' in the NEXT month.
    await mk("prijata", 0);
    await mk("stornovana", 0);
    await mk("prijata", 1);

    // Whole month: both same-month orders count -> 2 x 1000 = 2000 (net).
    const whole = await getOrderTotals({
      month: STATS_MONTH,
      year: STATS_YEAR,
    });
    expect(amountFor(whole.totals, "CZK")).toBe(2000);

    // Status filter narrows to the single 'prijata' in the target month -> 1000.
    const onlyPrijata = await getOrderTotals({
      month: STATS_MONTH,
      year: STATS_YEAR,
      status: "prijata",
    });
    expect(amountFor(onlyPrijata.totals, "CZK")).toBe(1000);

    // Next month sees only the one 'prijata' there -> 1000.
    const nextMonth = await getOrderTotals({
      month: STATS_MONTH + 1,
      year: STATS_YEAR,
    });
    expect(amountFor(nextMonth.totals, "CZK")).toBe(1000);
  });
});

describe("getIssuedOrderTotals (issued orders) per-currency aggregation", () => {
  it("sums NET total per currency over the full filtered set", async () => {
    // order_date is settable at create, so no post-create pin needed. NET only
    // (computeIssuedOrderTotals — issued orders carry no VAT).
    //   CZK #1: 2 x 1000 = 2000
    //   CZK #2: 1 x 500  =  500   => CZK total 2500
    //   EUR   : 3 x 100  =  300   => EUR total  300
    const dateStr = `${STATS_YEAR}-0${STATS_MONTH}-15`;
    const mk = async (currency: string, qty: number, price: number) => {
      const o = await createIssuedOrder({
        currency,
        order_date: dateStr,
        items: [{ description: "X", quantity: qty, unit_price: price }],
      });
      if ("error" in o) throw new Error(`createIssuedOrder failed: ${o.error}`);
      createdIssuedIds.push(o.id);
      return o.id;
    };

    await mk("CZK", 2, 1000);
    await mk("CZK", 1, 500);
    await mk("EUR", 3, 100);

    const { totals } = await getIssuedOrderTotals({
      month: STATS_MONTH,
      year: STATS_YEAR,
    });
    expect(amountFor(totals, "CZK")).toBe(2500);
    expect(amountFor(totals, "EUR")).toBe(300);
  });

  it("respects the where: a different month and a status filter change the result", async () => {
    const inMonth = `${STATS_YEAR}-0${STATS_MONTH}-15`;
    const nextMonth = `${STATS_YEAR}-0${STATS_MONTH + 1}-15`;

    // Target month: one draft, one already-sent (both 1000 net). Next month: one.
    const draft = await createIssuedOrder({
      currency: "CZK",
      order_date: inMonth,
      items: [{ description: "X", quantity: 1, unit_price: 1000 }],
    });
    if ("error" in draft)
      throw new Error(`createIssuedOrder failed: ${draft.error}`);
    createdIssuedIds.push(draft.id);

    const sent = await createIssuedOrder({
      status: "sent",
      currency: "CZK",
      order_date: inMonth,
      items: [{ description: "X", quantity: 1, unit_price: 1000 }],
    });
    if ("error" in sent)
      throw new Error(`createIssuedOrder failed: ${sent.error}`);
    createdIssuedIds.push(sent.id);

    const next = await createIssuedOrder({
      currency: "CZK",
      order_date: nextMonth,
      items: [{ description: "X", quantity: 1, unit_price: 1000 }],
    });
    if ("error" in next)
      throw new Error(`createIssuedOrder failed: ${next.error}`);
    createdIssuedIds.push(next.id);

    // Whole target month: both count -> 2 x 1000 = 2000 (net).
    const whole = await getIssuedOrderTotals({
      month: STATS_MONTH,
      year: STATS_YEAR,
    });
    expect(amountFor(whole.totals, "CZK")).toBe(2000);

    // Status filter narrows to the single 'sent' in the target month -> 1000.
    const onlySent = await getIssuedOrderTotals({
      month: STATS_MONTH,
      year: STATS_YEAR,
      status: "sent",
    });
    expect(amountFor(onlySent.totals, "CZK")).toBe(1000);

    // Next month sees only the one order there -> 1000.
    const nextStats = await getIssuedOrderTotals({
      month: STATS_MONTH + 1,
      year: STATS_YEAR,
    });
    expect(amountFor(nextStats.totals, "CZK")).toBe(1000);
  });
});
