import type { Transaction } from "../generated/kysely-tailordb";
import {
  PartnerNotFoundError,
  InvalidBankNameError,
  InvalidAccountHolderNameError,
  MissingAccountIdentifierError,
  CurrencyNotFoundError,
} from "../lib/errors.generated";
import type { defineModule as definePrimitivesModule } from "../../primitives";
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";

type PrimitivesModule = ReturnType<typeof definePrimitivesModule>;
type PrimitivesQueries = PrimitivesModule["queries"];

export interface CreatePartnerBankAccountInput {
  partnerId: string;
  bankName: string;
  accountHolderName: string;
  accountNumber?: string;
  routingNumber?: string;
  iban?: string;
  swiftBic?: string;
  currencyId: string;
}

/**
 * Function: createPartnerBankAccount
 *
 * Adds a bank account to a business partner. The first account is
 * automatically set as default. At least one of accountNumber or iban
 * is required.
 */
export async function run<CF extends Record<string, unknown>>(
  db: Transaction,
  input: CreatePartnerBankAccountInput & CF,
  ctx: CommandContext,
  primitivesQueries?: Pick<PrimitivesQueries, "getCurrency">,
) {
  const {
    partnerId,
    bankName,
    accountHolderName,
    accountNumber,
    routingNumber,
    iban,
    swiftBic,
    currencyId,
    ...customFields
  } = input;

  // 1. Check partner exists
  const partner = await db
    .selectFrom("BusinessPartner")
    .selectAll()
    .where("id", "=", partnerId)
    .executeTakeFirst();

  if (!partner) {
    return err(new PartnerNotFoundError(partnerId));
  }

  // 2. Validate bankName non-empty
  if (!bankName?.trim()) {
    return err(new InvalidBankNameError(bankName ?? ""));
  }

  // 3. Validate accountHolderName non-empty
  if (!accountHolderName?.trim()) {
    return err(new InvalidAccountHolderNameError(accountHolderName ?? ""));
  }

  // 4. At least one of accountNumber or iban required
  if (!accountNumber?.trim() && !iban?.trim()) {
    return err(new MissingAccountIdentifierError(partnerId));
  }

  // 5. Validate currency exists
  if (primitivesQueries?.getCurrency) {
    const currencyResult = await primitivesQueries.getCurrency(db, { id: currencyId }, ctx);
    if (!currencyResult.ok || !currencyResult.value.currency) {
      return err(new CurrencyNotFoundError(currencyId));
    }
  }

  // 6. Check if partner has existing accounts to determine isDefault
  const existingAccounts = await db
    .selectFrom("PartnerBankAccount")
    .select("id")
    .where("partnerId", "=", partnerId)
    .execute();

  const isDefault = existingAccounts.length === 0;

  // 7. Create bank account
  const bankAccount = await db
    .insertInto("PartnerBankAccount")
    .values({
      ...(customFields as Record<string, unknown>),
      partnerId,
      bankName,
      accountHolderName,
      accountNumber: accountNumber ?? null,
      routingNumber: routingNumber ?? null,
      iban: iban ?? null,
      swiftBic: swiftBic ?? null,
      currencyId,
      isDefault,
      updatedAt: null,
    })
    .returningAll()
    .executeTakeFirstOrThrow();

  return ok({ partnerBankAccount: bankAccount });
}
