import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } from "../generated/kysely-tailordb";
import {
  CreditNoteNotEditableError,
  EmptySalesCreditNoteLinesError,
  LineNotFoundError,
  SalesCreditNoteNotFoundError,
} from "../lib/errors.generated";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import {
  baseCreditNote,
  baseCreditNoteLine,
  baseSalesInvoiceLine,
  issuedCreditNote,
} from "../testing/fixtures";
import { run } from "./updateSalesCreditNote";

describe("updateSalesCreditNote", () => {
  it("updates draft credit-note metadata", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updated = { ...baseCreditNote, reason: "Updated reason" };
    mock.enqueueResults(
      [baseCreditNote], // SalesCreditNote
      [baseCreditNoteLine], // SalesCreditNoteLine (current lines)
      [updated], // updateTable SalesCreditNote
    );

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

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

  it("updates a line in place so its id and createdAt survive the edit", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseCreditNote], // SalesCreditNote
      [baseCreditNoteLine], // SalesCreditNoteLine (current lines)
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseCreditNote.id,
          updateLines: [{ lineId: baseCreditNoteLine.id, linePatch: { amount: 250 } }],
        },
        commandCtx,
      ),
    );

    expectOk(result);
    // Patched in place — neither deleted nor reinserted.
    expect(mock.deletes).toHaveLength(0);
    expect(mock.inserts).toHaveLength(0);
    expect(mock.updates[0].updateValues()).toEqual(expect.objectContaining({ amount: 250 }));
  });

  it("adds a credit-note line via addLines", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const invoiceLine = { ...baseSalesInvoiceLine, salesInvoiceId: baseCreditNote.salesInvoiceId };
    mock.enqueueResults(
      [baseCreditNote], // SalesCreditNote
      [baseCreditNoteLine], // SalesCreditNoteLine (current lines)
      [invoiceLine], // SalesInvoiceLine bulk fetch
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          id: baseCreditNote.id,
          addLines: [{ salesInvoiceLineId: invoiceLine.id, quantity: 1, amount: 50 }],
        },
        commandCtx,
      ),
    );

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

  it("removes a credit-note line via removeLineIds", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const secondLine = { ...baseCreditNoteLine, id: "sales-credit-note-line-2" };
    mock.enqueueResults(
      [baseCreditNote], // SalesCreditNote
      [baseCreditNoteLine, secondLine], // SalesCreditNoteLine (current lines)
    );

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

    expectOk(result);
    expect(mock.deletes).toHaveLength(1);
  });

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

    const result = await mock.withTx((trx) => run(trx, { id: "missing-credit-note" }, commandCtx));

    expectErr(result, SalesCreditNoteNotFoundError);
  });

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

    const result = await mock.withTx((trx) => run(trx, { id: issuedCreditNote.id }, commandCtx));

    expectErr(result, CreditNoteNotEditableError);
  });

  it("returns error when an update targets a missing line", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseCreditNote], // SalesCreditNote
      [baseCreditNoteLine], // SalesCreditNoteLine (current lines)
    );

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

    expectErr(result, LineNotFoundError);
  });

  it("returns error when a removal would leave the credit note without lines", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseCreditNote], // SalesCreditNote
      [baseCreditNoteLine], // SalesCreditNoteLine (current lines)
    );

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

    expectErr(result, EmptySalesCreditNoteLinesError);
  });
});
