import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } from "../generated/kysely-tailordb";
import {
  EmptySalesInvoiceLinesError,
  InvalidInvoiceLineError,
  InvoiceNotEditableError,
  LineNotFoundError,
  SalesInvoiceNotFoundError,
} from "../lib/errors.generated";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import {
  baseSalesInvoice,
  baseSalesInvoiceLine,
  baseSalesOrderLine,
  issuedSalesInvoice,
} from "../testing/fixtures";
import { run } from "./updateSalesInvoice";

describe("updateSalesInvoice", () => {
  it("updates draft invoice metadata", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseSalesInvoice],
      [baseSalesInvoiceLine],
      [{ ...baseSalesInvoice, notes: "Updated", updatedAt: new Date() }],
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseSalesInvoice.id,
          headerPatch: { notes: "Updated" },
        },
        commandCtx,
      ),
    );

    expectOk(result);
    expect(mock.updates[0].updateValues()).toEqual(expect.objectContaining({ notes: "Updated" }));
  });

  it("updates a line in place so its id and createdAt survive the edit", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseSalesInvoice], [baseSalesInvoiceLine]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseSalesInvoice.id,
          updateLines: [{ lineId: baseSalesInvoiceLine.id, linePatch: { quantity: 9 } }],
        },
        commandCtx,
      ),
    );

    expectOk(result);
    expect(mock.deletes.length).toBe(0);
    expect(mock.inserts.length).toBe(0);
    expect(mock.updates[0].updateValues()).toEqual(expect.objectContaining({ quantity: 9 }));
  });

  it("adds an invoice line via addLines", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const orderLine = { ...baseSalesOrderLine, salesOrderId: baseSalesInvoice.salesOrderId };
    mock.enqueueResults([baseSalesInvoice], [baseSalesInvoiceLine], [orderLine]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseSalesInvoice.id,
          addLines: [{ salesOrderLineId: orderLine.id, quantity: 3 }],
        },
        commandCtx,
      ),
    );

    expectOk(result);
    expect(mock.inserts[mock.inserts.length - 1].insertRows()).toHaveLength(1);
  });

  it("removes an invoice line via removeLineIds", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseSalesInvoice],
      [baseSalesInvoiceLine, { ...baseSalesInvoiceLine, id: "sales-invoice-line-2" }],
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseSalesInvoice.id,
          removeLineIds: [baseSalesInvoiceLine.id],
        },
        commandCtx,
      ),
    );

    expectOk(result);
    expect(mock.deletes.length).toBe(1);
  });

  it("returns error when invoice does not exist", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);

    const result = await mock.withTx((trx) =>
      run(trx, { id: baseSalesInvoice.id, headerPatch: { notes: "Updated" } }, commandCtx),
    );

    expectErr(result, SalesInvoiceNotFoundError);
  });

  it("returns error when updating an issued invoice", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([issuedSalesInvoice]);

    const result = await mock.withTx((trx) =>
      run(trx, { id: issuedSalesInvoice.id, headerPatch: { notes: "Updated" } }, commandCtx),
    );

    expectErr(result, InvoiceNotEditableError);
  });

  it("returns error when an update targets a missing line", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseSalesInvoice], [baseSalesInvoiceLine]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseSalesInvoice.id,
          updateLines: [{ lineId: "missing-line", linePatch: { quantity: 9 } }],
        },
        commandCtx,
      ),
    );

    expectErr(result, LineNotFoundError);
  });

  it("returns error when a removal would leave the invoice without lines", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseSalesInvoice], [baseSalesInvoiceLine]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseSalesInvoice.id,
          removeLineIds: [baseSalesInvoiceLine.id],
        },
        commandCtx,
      ),
    );

    expectErr(result, EmptySalesInvoiceLinesError);
  });

  it("returns error when an added line references an invalid sales-order line", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseSalesInvoice], [baseSalesInvoiceLine], []);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseSalesInvoice.id,
          addLines: [{ salesOrderLineId: "missing-order-line", quantity: 3 }],
        },
        commandCtx,
      ),
    );

    expectErr(result, InvalidInvoiceLineError);
  });
});
