import type { CommandContext } from "@tailor-platform/erp-kit/core";
import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { GetCurrencyInput } from "../../primitives/query/getCurrency";
import type { Namespace } from "../generated/kysely-tailordb";
import {
  PartnerNotFoundError,
  InvalidBankNameError,
  InvalidAccountHolderNameError,
  MissingAccountIdentifierError,
  CurrencyNotFoundError,
} from "../lib/errors.generated";
import {
  baseActivePartner,
  baseDraftPartner,
  baseInactivePartner,
  baseDefaultBankAccount,
  baseSecondBankAccount,
} from "../testing/fixtures";
import { run } from "./createPartnerBankAccount";

describe("createPartnerBankAccount", () => {
  const ctx: CommandContext = {
    actorId: "test-actor",
    permissions: ["business-partner:partner:createPartnerBankAccount"],
  };

  const baseCurrency = {
    id: "currency-usd",
    code: "USD",
    name: "US Dollar",
    symbol: "$",
    decimalPlaces: 2,
    status: "ACTIVE" as const,
    isBaseCurrency: true,
    createdAt: new Date("2024-01-01"),
    updatedAt: null,
  };

  const mockGetCurrency = (_db: unknown, input: GetCurrencyInput, _ctx: unknown) =>
    Promise.resolve({
      ok: true as const,
      value: {
        currency: "id" in input && input.id === "currency-usd" ? baseCurrency : null,
      },
    });
  const primitivesQueries = { getCurrency: mockGetCurrency };

  const baseInput = {
    partnerId: "partner-2",
    bankName: "First National Bank",
    accountHolderName: "Global Supplies Inc",
    accountNumber: "1234567890",
    currencyId: "currency-usd",
  };

  it("creates bank account for partner", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseActivePartner], [], [{ ...baseDefaultBankAccount }]);

    const result = await mock.withTx((trx) => run(trx, baseInput, ctx, primitivesQueries));

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.partnerBankAccount).toEqual(baseDefaultBankAccount);
    }
  });

  it("auto-sets first account as default", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseActivePartner],
      [], // no existing accounts
      [{ ...baseDefaultBankAccount, isDefault: true }],
    );

    await mock.withTx((trx) => run(trx, baseInput, ctx, primitivesQueries));

    expect(mock.inserts[0].insertValues()).toEqual(expect.objectContaining({ isDefault: true }));
  });

  it("does not set default when partner already has accounts", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseActivePartner],
      [{ id: "bank-1" }], // existing accounts
      [{ ...baseDefaultBankAccount, isDefault: false }],
    );

    await mock.withTx((trx) => run(trx, baseInput, ctx, primitivesQueries));

    expect(mock.inserts[0].insertValues()).toEqual(expect.objectContaining({ isDefault: false }));
  });

  it("creates bank account with iban", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseActivePartner],
      [],
      [{ ...baseSecondBankAccount, partnerId: "partner-2", isDefault: true }],
    );

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          partnerId: "partner-2",
          bankName: "Deutsche Bank",
          accountHolderName: "Global Supplies GmbH",
          iban: "DE89370400440532013000",
          currencyId: "currency-usd",
        },
        ctx,
        primitivesQueries,
      ),
    );

    expect(result.ok).toBe(true);
    expect(mock.inserts[0].insertValues()).toEqual(
      expect.objectContaining({ iban: "DE89370400440532013000" }),
    );
  });

  it("creates bank account with both accountNumber and iban", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseActivePartner],
      [],
      [{ ...baseDefaultBankAccount, iban: "DE89370400440532013000" }],
    );

    const result = await mock.withTx((trx) =>
      run(trx, { ...baseInput, iban: "DE89370400440532013000" }, ctx, primitivesQueries),
    );

    expect(result.ok).toBe(true);
    expect(mock.inserts[0].insertValues()).toEqual(
      expect.objectContaining({
        accountNumber: "1234567890",
        iban: "DE89370400440532013000",
      }),
    );
  });

  it("creates bank account with routingNumber", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseActivePartner],
      [],
      [{ ...baseDefaultBankAccount, routingNumber: "021000021" }],
    );

    const result = await mock.withTx((trx) =>
      run(trx, { ...baseInput, routingNumber: "021000021" }, ctx, primitivesQueries),
    );

    expect(result.ok).toBe(true);
    expect(mock.inserts[0].insertValues()).toEqual(
      expect.objectContaining({ routingNumber: "021000021" }),
    );
  });

  it("creates bank account with swiftBic", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseActivePartner],
      [],
      [{ ...baseDefaultBankAccount, swiftBic: "DEUTDEDB" }],
    );

    const result = await mock.withTx((trx) =>
      run(trx, { ...baseInput, swiftBic: "DEUTDEDB" }, ctx, primitivesQueries),
    );

    expect(result.ok).toBe(true);
    expect(mock.inserts[0].insertValues()).toEqual(
      expect.objectContaining({ swiftBic: "DEUTDEDB" }),
    );
  });

  it("creates bank account for DRAFT partner", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseDraftPartner],
      [],
      [{ ...baseDefaultBankAccount, partnerId: "partner-1" }],
    );

    const result = await mock.withTx((trx) =>
      run(trx, { ...baseInput, partnerId: "partner-1" }, ctx, primitivesQueries),
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.partnerBankAccount.partnerId).toBe("partner-1");
    }
  });

  it("creates bank account for INACTIVE partner", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseInactivePartner],
      [],
      [{ ...baseDefaultBankAccount, partnerId: "partner-3" }],
    );

    const result = await mock.withTx((trx) =>
      run(trx, { ...baseInput, partnerId: "partner-3" }, ctx, primitivesQueries),
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.partnerBankAccount.partnerId).toBe("partner-3");
    }
  });

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

    const result = await mock.withTx((trx) => run(trx, baseInput, ctx, primitivesQueries));

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(PartnerNotFoundError);
    }
  });

  it("returns error when bank name is empty", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseActivePartner]);

    const result = await mock.withTx((trx) =>
      run(trx, { ...baseInput, bankName: "" }, ctx, primitivesQueries),
    );

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidBankNameError);
    }
  });

  it("returns error when account holder name is empty", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseActivePartner]);

    const result = await mock.withTx((trx) =>
      run(trx, { ...baseInput, accountHolderName: "" }, ctx, primitivesQueries),
    );

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidAccountHolderNameError);
    }
  });

  it("returns error when neither account number nor iban provided", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseActivePartner]);

    const result = await mock.withTx((trx) =>
      run(trx, { ...baseInput, accountNumber: undefined, iban: undefined }, ctx, primitivesQueries),
    );

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(MissingAccountIdentifierError);
    }
  });

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

    const result = await mock.withTx((trx) =>
      run(trx, { ...baseInput, currencyId: "currency-nonexistent" }, ctx, primitivesQueries),
    );

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(CurrencyNotFoundError);
    }
  });
});
