import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { GetCurrencyInput } from "../../primitives/query/getCurrency";
import type { Transaction } 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";
import type { CommandContext } from "@tailor-platform/erp-kit/module";

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

  const baseCurrency = {
    id: "currency-usd",
    code: "USD",
    name: "US Dollar",
    symbol: "$",
    decimalPlaces: 2,
    isActive: true,
    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 { db, spies } = createMockDb<Transaction>();
    // partner exists
    spies.select.mockReturnValueOnce(baseActivePartner);
    // no existing accounts
    spies.select.mockReturnValueOnce([]);
    // insert returns new account
    spies.insert.mockReturnValueOnce({ ...baseDefaultBankAccount });

    const result = await run(db, 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 { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseActivePartner);
    // no existing accounts — empty array
    spies.select.mockReturnValueOnce([]);
    spies.insert.mockReturnValueOnce({ ...baseDefaultBankAccount, isDefault: true });

    await run(db, baseInput, ctx, primitivesQueries);

    expect(spies.values).toHaveBeenCalledWith(expect.objectContaining({ isDefault: true }));
  });

  it("does not set default when partner already has accounts", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseActivePartner);
    // existing accounts
    spies.select.mockReturnValueOnce([{ id: "bank-1" }]);
    spies.insert.mockReturnValueOnce({ ...baseDefaultBankAccount, isDefault: false });

    await run(db, baseInput, ctx, primitivesQueries);

    expect(spies.values).toHaveBeenCalledWith(expect.objectContaining({ isDefault: false }));
  });

  it("creates bank account with iban", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseActivePartner);
    spies.select.mockReturnValueOnce([]);
    spies.insert.mockReturnValueOnce({
      ...baseSecondBankAccount,
      partnerId: "partner-2",
      isDefault: true,
    });

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

    expect(result.ok).toBe(true);
    expect(spies.values).toHaveBeenCalledWith(
      expect.objectContaining({ iban: "DE89370400440532013000" }),
    );
  });

  it("creates bank account with both accountNumber and iban", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseActivePartner);
    spies.select.mockReturnValueOnce([]);
    spies.insert.mockReturnValueOnce({ ...baseDefaultBankAccount, iban: "DE89370400440532013000" });

    const result = await run(
      db,
      {
        ...baseInput,
        iban: "DE89370400440532013000",
      },
      ctx,
      primitivesQueries,
    );

    expect(result.ok).toBe(true);
    expect(spies.values).toHaveBeenCalledWith(
      expect.objectContaining({
        accountNumber: "1234567890",
        iban: "DE89370400440532013000",
      }),
    );
  });

  it("creates bank account with routingNumber", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseActivePartner);
    spies.select.mockReturnValueOnce([]);
    spies.insert.mockReturnValueOnce({ ...baseDefaultBankAccount, routingNumber: "021000021" });

    const result = await run(
      db,
      {
        ...baseInput,
        routingNumber: "021000021",
      },
      ctx,
      primitivesQueries,
    );

    expect(result.ok).toBe(true);
    expect(spies.values).toHaveBeenCalledWith(
      expect.objectContaining({ routingNumber: "021000021" }),
    );
  });

  it("creates bank account with swiftBic", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseActivePartner);
    spies.select.mockReturnValueOnce([]);
    spies.insert.mockReturnValueOnce({ ...baseDefaultBankAccount, swiftBic: "DEUTDEDB" });

    const result = await run(
      db,
      {
        ...baseInput,
        swiftBic: "DEUTDEDB",
      },
      ctx,
      primitivesQueries,
    );

    expect(result.ok).toBe(true);
    expect(spies.values).toHaveBeenCalledWith(expect.objectContaining({ swiftBic: "DEUTDEDB" }));
  });

  it("creates bank account for DRAFT partner", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseDraftPartner);
    spies.select.mockReturnValueOnce([]);
    spies.insert.mockReturnValueOnce({
      ...baseDefaultBankAccount,
      partnerId: "partner-1",
    });

    const result = await run(db, { ...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 { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseInactivePartner);
    spies.select.mockReturnValueOnce([]);
    spies.insert.mockReturnValueOnce({
      ...baseDefaultBankAccount,
      partnerId: "partner-3",
    });

    const result = await run(db, { ...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 { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(undefined);

    const result = await run(db, 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 { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseActivePartner);

    const result = await run(db, { ...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 { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseActivePartner);

    const result = await run(db, { ...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 { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseActivePartner);

    const result = await run(
      db,
      { ...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 { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseActivePartner);

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

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