import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  JournalEntryNotFoundError,
  InvalidStatusForUpdateError,
  DuplicateReferenceNumberError,
} from "../lib/errors.generated";
import { baseJournalEntry, postedJournalEntry, reversedJournalEntry } from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./updateJournalEntry";

describe("updateJournalEntry", () => {
  it("returns error when journal entry does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(undefined);

    const result = await run(
      db,
      {
        journalEntryId: "missing-entry",
        description: "Updated",
      },
      commandCtx,
    );

    expectErr(result, JournalEntryNotFoundError);
  });

  it("returns error when journal entry is in POSTED status", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(postedJournalEntry);

    const result = await run(
      db,
      {
        journalEntryId: postedJournalEntry.id,
        description: "Updated",
      },
      commandCtx,
    );

    expectErr(result, InvalidStatusForUpdateError);
  });

  it("returns error when journal entry is in REVERSED status", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(reversedJournalEntry);

    const result = await run(
      db,
      {
        journalEntryId: reversedJournalEntry.id,
        description: "Updated",
      },
      commandCtx,
    );

    expectErr(result, InvalidStatusForUpdateError);
  });

  it("updates description on a DRAFT journal entry", async () => {
    const updatedEntry = { ...baseJournalEntry, description: "New description" };
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseJournalEntry);
    spies.update.mockReturnValueOnce(updatedEntry);

    const result = await run(
      db,
      {
        journalEntryId: baseJournalEntry.id,
        description: "New description",
      },
      commandCtx,
    );

    const value = expectOk(result);
    expect(value.journalEntry.description).toBe("New description");
  });

  it("updates source document reference on a DRAFT journal entry", async () => {
    const updatedEntry = { ...baseJournalEntry, sourceDocumentReference: "INV-999" };
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseJournalEntry);
    spies.update.mockReturnValueOnce(updatedEntry);

    const result = await run(
      db,
      {
        journalEntryId: baseJournalEntry.id,
        sourceDocumentReference: "INV-999",
      },
      commandCtx,
    );

    const value = expectOk(result);
    expect(value.journalEntry.sourceDocumentReference).toBe("INV-999");
  });

  it("updates entry date on a DRAFT journal entry", async () => {
    const newDate = new Date("2024-01-20");
    const updatedEntry = { ...baseJournalEntry, entryDate: newDate };
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseJournalEntry);
    spies.update.mockReturnValueOnce(updatedEntry);

    const result = await run(
      db,
      {
        journalEntryId: baseJournalEntry.id,
        entryDate: newDate,
      },
      commandCtx,
    );

    const value = expectOk(result);
    expect(value.journalEntry.entryDate).toEqual(newDate);
  });

  it("updates reference number on a DRAFT journal entry with uniqueness check", async () => {
    const updatedEntry = { ...baseJournalEntry, referenceNumber: "JE-NEW" };
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseJournalEntry) // entry lookup
      .mockReturnValueOnce(undefined); // no duplicate ref
    spies.update.mockReturnValueOnce(updatedEntry);

    const result = await run(
      db,
      {
        journalEntryId: baseJournalEntry.id,
        referenceNumber: "JE-NEW",
      },
      commandCtx,
    );

    const value = expectOk(result);
    expect(value.journalEntry.referenceNumber).toBe("JE-NEW");
  });

  it("returns error when updated reference number already exists", async () => {
    const existingOther = { ...baseJournalEntry, id: "other-entry", referenceNumber: "JE-TAKEN" };
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseJournalEntry) // entry lookup
      .mockReturnValueOnce(existingOther); // duplicate ref found

    const result = await run(
      db,
      {
        journalEntryId: baseJournalEntry.id,
        referenceNumber: "JE-TAKEN",
      },
      commandCtx,
    );

    expectErr(result, DuplicateReferenceNumberError);
  });

  it("emits audit event recording previous and new field values and acting user", async () => {
    const updatedEntry = { ...baseJournalEntry, description: "Audited update" };
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseJournalEntry);
    spies.update.mockReturnValueOnce(updatedEntry);

    const result = await run(
      db,
      {
        journalEntryId: baseJournalEntry.id,
        description: "Audited update",
      },
      commandCtx,
    );

    expectOk(result);
    expect(spies.update).toHaveBeenCalled();
  });
});
