TransferCryptoRequest.kt

package com.distasilucas.cryptobalancetracker.model.request.crypto

import com.distasilucas.cryptobalancetracker.constants.TO_PLATFORM_ID_UUID
import com.distasilucas.cryptobalancetracker.constants.NETWORK_FEE_DIGITS
import com.distasilucas.cryptobalancetracker.constants.NETWORK_FEE_MIN
import com.distasilucas.cryptobalancetracker.constants.NETWORK_FEE_NOT_NULL
import com.distasilucas.cryptobalancetracker.constants.QUANTITY_TO_TRANSFER_DECIMAL_MAX
import com.distasilucas.cryptobalancetracker.constants.QUANTITY_TO_TRANSFER_DIGITS
import com.distasilucas.cryptobalancetracker.constants.QUANTITY_TO_TRANSFER_NOT_NULL
import com.distasilucas.cryptobalancetracker.constants.QUANTITY_TO_TRANSFER_POSITIVE
import com.distasilucas.cryptobalancetracker.constants.TO_PLATFORM_ID_NOT_BLANK
import com.distasilucas.cryptobalancetracker.constants.USER_CRYPTO_ID_UUID
import com.distasilucas.cryptobalancetracker.constants.USER_CRYPTO_ID_NOT_BLANK
import com.distasilucas.cryptobalancetracker.model.response.crypto.FromPlatform
import com.distasilucas.cryptobalancetracker.model.response.crypto.ToPlatform
import com.distasilucas.cryptobalancetracker.model.response.crypto.TransferCryptoResponse
import jakarta.validation.constraints.DecimalMax
import jakarta.validation.constraints.Digits
import jakarta.validation.constraints.Min
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.Positive
import org.hibernate.validator.constraints.UUID
import java.math.BigDecimal

data class TransferCryptoRequest(
  @field: NotBlank(message = USER_CRYPTO_ID_NOT_BLANK)
  @field: UUID(message = USER_CRYPTO_ID_UUID)
  val userCryptoId: String?,

  @field: NotNull(message = QUANTITY_TO_TRANSFER_NOT_NULL)
  @field: Digits(
    integer = 16,
    fraction = 12,
    message = QUANTITY_TO_TRANSFER_DIGITS
  )
  @field: DecimalMax(
    value = "9999999999999999.999999999999",
    message = QUANTITY_TO_TRANSFER_DECIMAL_MAX
  )
  @field: Positive(message = QUANTITY_TO_TRANSFER_POSITIVE)
  val quantityToTransfer: BigDecimal?,

  @field: NotNull(message = NETWORK_FEE_NOT_NULL)
  @field: Digits(
    integer = 16,
    fraction = 12,
    message = NETWORK_FEE_DIGITS
  )
  @field: Min(value = 0, message = NETWORK_FEE_MIN)
  val networkFee: BigDecimal?,

  val sendFullQuantity: Boolean? = false,

  @field: NotBlank(message = TO_PLATFORM_ID_NOT_BLANK)
  @field: UUID(message = TO_PLATFORM_ID_UUID)
  val toPlatformId: String?
) {

  fun toTransferCryptoResponse(
    remainingCryptoQuantity: BigDecimal,
    newQuantity: BigDecimal,
    quantityToSendReceive: BigDecimal
  ): TransferCryptoResponse {
    return TransferCryptoResponse(
      fromPlatform = FromPlatform(
        userCryptoId = userCryptoId!!,
        networkFee = networkFee!!.toPlainString(),
        quantityToTransfer = quantityToTransfer!!.toPlainString(),
        totalToSubtract = calculateTotalToSubtract(remainingCryptoQuantity).toPlainString(),
        quantityToSendReceive = quantityToSendReceive.toPlainString(),
        remainingCryptoQuantity = remainingCryptoQuantity.toPlainString(),
        sendFullQuantity = sendFullQuantity!!
      ),
      toPlatform = ToPlatform(
        platformId = toPlatformId!!,
        newQuantity = newQuantity.toPlainString()
      )
    )
  }

  private fun calculateTotalToSubtract(remainingCryptoQuantity: BigDecimal): BigDecimal {
    return if (sendFullQuantity == true) {
      if (remainingCryptoQuantity > BigDecimal.ZERO) networkFee!!.add(quantityToTransfer) else quantityToTransfer!!
    } else {
      quantityToTransfer!!
    }
  }

  fun calculateQuantityToSendReceive(remainingCryptoQuantity: BigDecimal, availableQuantity: BigDecimal): BigDecimal {
    val quantityToSendReceive = if (sendFullQuantity == true) {
      if (remainingCryptoQuantity == BigDecimal.ZERO) availableQuantity.subtract(networkFee) else quantityToTransfer!!
    } else {
      quantityToTransfer!!.subtract(networkFee)
    }

    return quantityToSendReceive.stripTrailingZeros()
  }

  fun calculateRemainingCryptoQuantity(availableQuantity: BigDecimal): BigDecimal {
    val remaining = if (sendFullQuantity == true) {
      availableQuantity.subtract(quantityToTransfer!!.add(networkFee))
    } else {
      availableQuantity.subtract(quantityToTransfer)
    }

    return if (remaining < BigDecimal.ZERO) BigDecimal.ZERO else remaining.stripTrailingZeros()
  }

  fun hasInsufficientBalance(availableQuantity: BigDecimal) = availableQuantity < quantityToTransfer || networkFee!! > availableQuantity

}