import { describe, it, expect, beforeAll, afterAll } from "vitest";
import Fastify from "fastify";
import jwt from "jsonwebtoken";
import prisma from "../config/database";
import { config } from "../config/env";
import { renderOrderConfirmationHtml } from "../routes/admin/orders-pdf";
import offersPdfRoutes from "../routes/admin/offers-pdf";
import invoicesPdfRoutes from "../routes/admin/invoices-pdf";
import { createInvoice } from "../services/invoices.service";

// Offers, received orders (their confirmation PDF) and issued orders are NOT
// tax documents — VAT must never appear on them. These tests pin that contract
// for the confirmation and offer PDFs: no VAT columns/rows/notices, and the
// grand total is "Celkem bez DPH" / "Total excl. VAT". (The issued-order PDF
// is covered in issued-orders.test.ts.)

// The explanatory prices-excl.-VAT notice was removed at user request — the
// total label alone carries the information. Pin its absence too.
const CS_NOTE = "Ceny jsou uvedeny bez DPH";
const EN_NOTE = "Prices are exclusive of VAT";

describe("renderOrderConfirmationHtml (order confirmation PDF)", () => {
  const order = {
    order_number: "26730042",
    customer_order_number: "PO-XYZ",
    created_at: new Date("2026-06-09T12:00:00"),
    currency: "CZK",
    notes: null,
    customers: null,
  };
  const items = [
    {
      description: "Materiál",
      quantity: 2,
      unit: "ks",
      unit_price: 100,
      is_included_in_total: true,
    },
  ];

  it("cs: NET total labeled 'Celkem bez DPH', no VAT columns or notice", () => {
    const html = renderOrderConfirmationHtml(order, items, null, "cs", "Jan");
    expect(html).not.toContain("%DPH");
    expect(html).not.toContain(">DPH<");
    expect(html).not.toContain("Mezisoučet");
    // Line total = netto (2 × 100), no VAT-on-top anywhere.
    expect(html).toContain('<td class="right total-cell">200,00</td>');
    expect(html).toContain("Celkem bez DPH");
    expect(html).not.toContain(CS_NOTE);
  });

  it("en: 'Total excl. VAT', no VAT columns or notice", () => {
    const html = renderOrderConfirmationHtml(order, items, null, "en", "Jan");
    expect(html).not.toContain("VAT%");
    expect(html).toContain("Total excl. VAT");
    expect(html).not.toContain(EN_NOTE);
  });

  it("excludes not-included lines from the NET total", () => {
    const html = renderOrderConfirmationHtml(
      order,
      [
        ...items,
        {
          description: "Mimo cenu",
          quantity: 1,
          unit: "ks",
          unit_price: 999,
          is_included_in_total: false,
        },
      ],
      null,
      "cs",
      "Jan",
    );
    // Grand total stays 200,00 — the excluded line doesn't count.
    expect(html).toContain("200,00 CZK");
  });

  it("uses the shared NBSP-thousands formatter (pdf-shared extraction marker)", () => {
    const html = renderOrderConfirmationHtml(
      order,
      [
        {
          description: "Velká položka",
          quantity: 2,
          unit: "ks",
          unit_price: 1000,
          is_included_in_total: true,
        },
      ],
      null,
      "cs",
      "Jan",
    );
    // 2 × 1000 = 2 000,00 with the NBSP (U+00A0) thousands separator.
    expect(html).toContain("2 000,00 CZK");
    expect(html).not.toContain("1 199,00 CZK");
  });
});

/* -------------------------------------------------------------------------- */
/* Offer PDF route (returns the HTML for the print view)                      */
/* -------------------------------------------------------------------------- */

let app: ReturnType<typeof Fastify> | null = null;
let adminToken = "";
const createdQuotationIds: number[] = [];
const createdInvoiceIds: number[] = [];

beforeAll(async () => {
  app = Fastify({ logger: false });
  await app.register(offersPdfRoutes, { prefix: "/api/admin/offers-pdf" });
  await app.register(invoicesPdfRoutes, { prefix: "/api/admin/invoices-pdf" });

  const admin = await prisma.users.findFirst({
    where: { roles: { name: "admin" } },
  });
  if (!admin) throw new Error("Test setup: admin user not found");
  adminToken = jwt.sign(
    { sub: admin.id, username: admin.username, role: "admin" },
    config.jwt.secret,
    { expiresIn: "15m" },
  );
});

afterAll(async () => {
  for (const id of createdQuotationIds)
    await prisma.quotations.deleteMany({ where: { id } });
  for (const id of createdInvoiceIds)
    await prisma.invoices.deleteMany({ where: { id } });
  if (app) await app.close();
});

describe("GET /api/admin/offers-pdf/:id (offer PDF html)", () => {
  it("renders NET-only totals with 'Celkem bez DPH', no notice", async () => {
    // Direct prisma insert (explicit number → no sequence consumed).
    const quotation = await prisma.quotations.create({
      data: {
        quotation_number: `Q-VATNOTE-${Date.now()}`,
        status: "active",
        currency: "CZK",
        language: "cs",
        quotation_items: {
          create: [
            {
              description: "Položka",
              quantity: 2,
              unit: "ks",
              unit_price: 1000,
              is_included_in_total: true,
              position: 0,
            },
          ],
        },
      },
    });
    createdQuotationIds.push(quotation.id);

    const res = await app!.inject({
      method: "GET",
      url: `/api/admin/offers-pdf/${quotation.id}`,
      headers: { Authorization: `Bearer ${adminToken}` },
    });
    expect(res.statusCode).toBe(200);
    const html = res.body;
    expect(html).toContain("Celkem bez DPH");
    expect(html).not.toContain(CS_NOTE);
    expect(html).not.toContain("%DPH");
    expect(html).not.toContain("Mezisoučet");
    expect(html).not.toContain("Celkem k úhradě");
  });

  it("renders a fractional quantity as '1,50', not rounded to '2'", async () => {
    const quotation = await prisma.quotations.create({
      data: {
        quotation_number: `Q-FRACQTY-${Date.now()}`,
        status: "active",
        currency: "CZK",
        language: "cs",
        quotation_items: {
          create: [
            {
              description: "Půldenní sazba",
              quantity: 1.5,
              unit: "ks",
              unit_price: 1000,
              is_included_in_total: true,
              position: 0,
            },
          ],
        },
      },
    });
    createdQuotationIds.push(quotation.id);

    const res = await app!.inject({
      method: "GET",
      url: `/api/admin/offers-pdf/${quotation.id}`,
      headers: { Authorization: `Bearer ${adminToken}` },
    });
    expect(res.statusCode).toBe(200);
    const html = res.body;
    // The old toFixed(0) ROUNDED 1.5 → "2"; integers still render without
    // decimals elsewhere (covered by the previous test's qty 2).
    expect(html).toContain("1,50 / ks");
    expect(html).not.toContain(">2 / ks<");
  });
});

/* -------------------------------------------------------------------------- */
/* Invoice PDF route — post-pdf-shared-extraction render marker               */
/* -------------------------------------------------------------------------- */

describe("GET /api/admin/invoices-pdf/:id (invoice PDF html)", () => {
  it("still renders the invoice HTML after the shared-helper extraction", async () => {
    // Draft → no number consumed; CZK → no CNB exchange-rate lookup.
    const invoice = await createInvoice({
      status: "draft",
      currency: "CZK",
      apply_vat: true,
      vat_rate: 21,
      items: [
        {
          description: "PDF-SHARED-MARKER",
          quantity: 2,
          unit_price: 1000,
          vat_rate: 21,
        },
      ],
    });
    createdInvoiceIds.push(invoice.id);

    const res = await app!.inject({
      method: "GET",
      url: `/api/admin/invoices-pdf/${invoice.id}`,
      headers: { Authorization: `Bearer ${adminToken}` },
    });
    expect(res.statusCode).toBe(200);
    const html = res.body;
    expect(html).toContain("FAKTURA - DAŇOVÝ DOKLAD");
    expect(html).toContain("PDF-SHARED-MARKER");
    // Shared NBSP formatNum: 2 × 1000 = 2 000,00 base.
    expect(html).toContain("2 000,00");
    expect(html).toContain("Celkem k úhradě");
  });
});
