import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } from "../generated/kysely-tailordb";
import { ACCOUNT_PAYABLE_RECOGNIZED } from "../lib/contracts";
import { CompanyNotFoundError } from "../lib/errors.generated";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { baseAccountingEvent, baseCompany } from "../testing/fixtures";
import { mockOrganizationQueries, type Company } from "../testing/moduleMocks";
import { run as runBase, type PostAccountingEventInput } from "./postAccountingEvent";

const input: PostAccountingEventInput = {
  companyId: baseCompany.id,
  eventKind: ACCOUNT_PAYABLE_RECOGNIZED,
  schemaVersion: "1",
  payload: {
    source: {
      documentType: "ACCOUNT_PAYABLE_DOCUMENT",
      documentId: "account-payable-document-1",
      documentNumber: "AP-10000001",
    },
    documentType: "INVOICE",
    supplierId: "supplier-1",
    documentDate: new Date("2026-01-10"),
    accountingDate: new Date("2026-01-15"),
    dueSchedule: [{ dueDate: new Date("2026-02-15"), amount: "1200.00" }],
    currencyId: "currency-1",
    currencyCode: "USD",
    totalAmount: "1200.00",
    payableAccountId: "account-payable-control",
    lines: [],
    distributions: [],
  },
};

const run = (
  db: Parameters<typeof runBase>[0],
  input: PostAccountingEventInput,
  ctx: Parameters<typeof runBase>[2],
  companies: Company[] = [baseCompany],
) => runBase(db, input, ctx, mockOrganizationQueries({ companies }));

function parseInsertedPayload(mock: ReturnType<typeof createKyselyMock<Namespace["main-db"]>>) {
  return JSON.parse(mock.inserts[0].insertRows()[0].payloadJson as string) as typeof input.payload;
}

function insertedAccountingEventRow(
  mock: ReturnType<typeof createKyselyMock<Namespace["main-db"]>>,
) {
  return mock.inserts[0].insertRows()[0] as {
    companyId: string;
    eventKind: string;
    schemaVersion: string;
    occurredAt: Date;
    payloadJson: string;
  };
}

describe("postAccountingEvent", () => {
  it("posts accounting event with typed payload", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAccountingEvent]);

    const result = await mock.withTx((trx) => run(trx, input, commandCtx));

    const value = expectOk(result);
    expect(value.accountingEvent.id).toBe(baseAccountingEvent.id);
    const inserted = insertedAccountingEventRow(mock);
    expect(inserted.companyId).toBe(baseCompany.id);
    expect(inserted.eventKind).toBe(ACCOUNT_PAYABLE_RECOGNIZED);
    expect(inserted.schemaVersion).toBe("1");
    expect(inserted.occurredAt).toBeInstanceOf(Date);
    expect(inserted.payloadJson).toBe(JSON.stringify(input.payload));
  });

  it("stringifies the typed payload before insert", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAccountingEvent]);

    const result = await mock.withTx((trx) => run(trx, input, commandCtx));

    expectOk(result);
    expect(mock.selects).toHaveLength(0);
    expect(typeof insertedAccountingEventRow(mock).payloadJson).toBe("string");
    expect(parseInsertedPayload(mock)).toEqual(JSON.parse(JSON.stringify(input.payload)));
  });

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

    const result = await mock.withTx((trx) => run(trx, input, commandCtx, []));

    expectErr(result, CompanyNotFoundError);
  });
});
