import { Transaction } from '@mysten/sui/transactions';
import { normalizeSuiObjectId } from '@mysten/sui/utils';

import { ChainID } from '../../chains.js';
import {
  CROSS_CHAIN_GUARD_ADDRESSES,
  NATIVE_SUI_TOKEN_ADDRESS,
  SUI_GUARD_COLLATERAL_TYPE,
  SUI_GUARD_STABLECOIN_TYPE,
  SUI_PACKAGE_ID,
} from '../../constants.js';
import { ValidationError } from '../../errors/index.js';
import { createSuiClient } from './client.js';
import type { CrossChainOrder } from '../orders/cross-chain.js';

/**
 * Generates Sui order transaction for creating an order
 * @param order The order to generate transaction for
 * @returns Object containing the transaction
 */
export async function getSuiOrderTransaction(order: CrossChainOrder): Promise<Transaction> {
  const userAddress = order.user;
  const client = createSuiClient();
  const coins = await client.getCoins({
    owner: userAddress,
    coinType: order.sourceTokenAddress,
  });

  const guardAddress = CROSS_CHAIN_GUARD_ADDRESSES[ChainID.Sui];

  const sourceTokenInCoins = coins.data.sort((a, b) => Number(b.balance) - Number(a.balance));

  if (sourceTokenInCoins.length === 0) {
    throw new ValidationError(`No token (${order.sourceTokenAddress}) to create order`);
  }

  const coinsInSum = sourceTokenInCoins.reduce((acc, coin) => acc + BigInt(coin.balance), 0n);

  if (coinsInSum < order.sourceTokenAmount) {
    throw new ValidationError(
      `Insufficient balance to create order. Total coins is ${coinsInSum}, but order requires ${order.sourceTokenAmount}`,
    );
  }

  const spendingGasCoin =
    normalizeSuiObjectId(order.sourceTokenAddress) === normalizeSuiObjectId(NATIVE_SUI_TOKEN_ADDRESS);

  const tx = new Transaction();
  const coinInArg = spendingGasCoin ? tx.gas : sourceTokenInCoins[0]!.coinObjectId;

  if (sourceTokenInCoins.length > 1) {
    // Merging coins IN into a single object just in case
    tx.mergeCoins(
      // merge into 1st coin object
      coinInArg,
      // merge the rest of coins
      sourceTokenInCoins.slice(1).map((coin) => coin.coinObjectId),
    );
  }

  const [coinIn] = tx.splitCoins(coinInArg, [order.sourceTokenAmount]);

  tx.moveCall({
    package: SUI_PACKAGE_ID,
    module: 'source_chain_guard',
    function: 'create_order',
    arguments: [
      tx.object(guardAddress), // arg0: &Guard<T0, T2>
      coinIn, // arg1: Coin<T1>
      tx.pure.u64(order.minStablecoinAmount), // arg2: min_stablecoins_amount
      tx.pure.u64(order.deadline * 1000), // arg3: deadline (converted to milliseconds)
      tx.pure.vector('u8', order.executionDetailsHashToBytes()), // arg4: execution_details_hash
      tx.object.clock(), // arg5: &Clock
    ],
    typeArguments: [SUI_GUARD_COLLATERAL_TYPE, order.sourceTokenAddress, SUI_GUARD_STABLECOIN_TYPE],
  });

  tx.setSender(userAddress);

  return tx;
}
