import type { Transaction } from "../generated/kysely-tailordb";
import {
  BankAccountNotFoundError,
  InvalidBankNameError,
  InvalidAccountHolderNameError,
  CurrencyNotFoundError,
  MissingAccountIdentifierError,
} 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 type UpdatePartnerBankAccountInput = {
  id: string;
  bankName?: string;
  accountHolderName?: string;
  accountNumber?: string;
  routingNumber?: string | null;
  iban?: string;
  swiftBic?: string | null;
  currencyId?: string;
};
/**
 * Function: updatePartnerBankAccount
 *
 * Updates mutable fields of an existing partner bank account. After
 * update, at least one of accountNumber or iban must remain present.
 */
export async function run<CF extends Record<string, unknown>>(
  db: Transaction,
  input: UpdatePartnerBankAccountInput & Omit<Partial<CF>, "status">,
  ctx: CommandContext,
  primitivesQueries?: Pick<PrimitivesQueries, "getCurrency">,
) {
  const {
    id,
    bankName,
    accountHolderName,
    accountNumber,
    routingNumber,
    iban,
    swiftBic,
    currencyId,
    ...customFields
  } = input;

  // 1. Check account exists
  const account = await db
    .selectFrom("PartnerBankAccount")
    .selectAll()
    .where("id", "=", id)
    .forUpdate()
    .executeTakeFirst();

  if (!account) {
    return err(new BankAccountNotFoundError(id));
  }

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

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

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

  // 5. After update, at least one of accountNumber or iban must remain
  const mergedAccountNumber = accountNumber ?? account.accountNumber;
  const mergedIban = iban ?? account.iban;

  if (!mergedAccountNumber?.trim() && !mergedIban?.trim()) {
    return err(new MissingAccountIdentifierError(id));
  }

  // 6. Build update payload
  const updates: Record<string, unknown> = {
    ...(customFields as Record<string, unknown>),
    updatedAt: new Date(),
  };
  if (bankName !== undefined) updates.bankName = bankName;
  if (accountHolderName !== undefined) updates.accountHolderName = accountHolderName;
  if (accountNumber !== undefined) updates.accountNumber = accountNumber ?? null;
  if (routingNumber !== undefined) updates.routingNumber = routingNumber ?? null;
  if (iban !== undefined) updates.iban = iban ?? null;
  if (swiftBic !== undefined) updates.swiftBic = swiftBic ?? null;
  if (currencyId !== undefined) updates.currencyId = currencyId;

  const updated = await db
    .updateTable("PartnerBankAccount")
    .set(updates)
    .where("id", "=", id)
    .returningAll()
    .executeTakeFirst();

  return ok({ bankAccount: updated! });
}
