#!/usr/bin/env bash

# docker-finance | modern accounting for the power-user
#
# Copyright (C) 2021-2025 Aaron Fiore (Founder, Evergreen Crypto LLC)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

#
# "Libraries"
#

[ -z "$DOCKER_FINANCE_CONTAINER_REPO" ] && exit 1
source "${DOCKER_FINANCE_CONTAINER_REPO}/src/hledger-flow/lib/lib_preprocess.bash" "$1" "$2"

#
# Implementation
#

[ -z "$global_year" ] && exit 1
[ -z "$global_subaccount" ] && exit 1

[ -z "$global_in_path" ] && exit 1
[ -z "$global_out_path" ] && exit 1

[ -z "$global_in_filename" ] && exit 1

#
# Unified format between all file types:
#
#   UID,Date,Type,OrderType,CurrencyOne,CurrencyTwo,Amount,Price,Fees,Destination,TXID,Direction,Subaccount
#

function parse_transfers()
{
  # NOTE: header can be variable, ineffective to assert_header
  # TODO: optimize: do successive test_headers and whichever wins, use that string so `xan select` is avoided
  local _header=""

  if lib_preprocess::test_header "info_feeCurrency"; then

    _header="info_eid,info_timestampms,info_type,info_currency,info_feeCurrency,info_amount,info_feeAmount"

    # On-chain
    lib_preprocess::test_header "info_destination" && _header+=",info_destination"
    lib_preprocess::test_header "info_txHash" && _header+=",info_txHash"

    # Off-chain (withdrawal)
    lib_preprocess::test_header "info_method" && _header+=",info_method"

    xan select "$_header" "$global_in_path" \
      | gawk -v global_year="$global_year" -v global_subaccount="$global_subaccount" -M -v PREC=100 \
        '{
           if (NR<2)
             next

           cmd = "date --utc \"+%F %T\" --date=@$(expr $(echo "$2"/1000 | bc))" | getline date

           if (date !~ global_year)
             next

           # NOTE: If info_method isnt ACH/Wire/SEN then it should be GUSD (exception being CreditCard)
           if ($10 == "ACH" || $10 == "Wire" || $10 == "SEN") {$4="USD"}
           else {gsub(/,USD,/,",GUSD,")}

           # Get/set info_method (if available)
           if ($10 == "ACH" || $10 == "Wire" || $10 == "SEN" || $10 == "CreditCard") {$8=$10}

           printf $1 OFS           # UID (info_eid)
           printf date OFS         # Date
           printf "TRANSFER" OFS   # Type
           printf $3 OFS           # OrderType (info_type)
           printf $4 OFS           # CurrencyOne (info_currency)
           printf $5 OFS           # CurrencyTwo (info_feeCurrency)

           # Amount
           # TODO: getline an gawk remove-zeros script
           # NOTE: .18 requires PREC passed to gawk
           info_amount=sprintf("%.18f", $6)
           if (info_amount ~ /^[0-9]*\.[0-9]+/)
             {
               sub(/0+$/, "", info_amount)
               sub(/\.$/, "", info_amount)
               sub(/^\./, "0.", info_amount)
             }
           printf info_amount OFS

           printf OFS              # Price (N/A)

           # Fees
           info_feeAmount=sprintf("%.18f", $7)
           if (info_feeAmount ~ /^[0-9]*\.[0-9]+/)
             {
               sub(/0+$/, "", info_feeAmount)
               sub(/\.$/, "", info_feeAmount)
               sub(/^\./, "0.", info_feeAmount)
             }
           printf info_feeAmount OFS

           printf OFS             # Cost-basis (N/A)
           printf $8 OFS          # Destination (info_destination)
           printf $9 OFS          # TXID (info_txHash)

           direction=($3 ~ /^Withdrawal$/ ? "OUT" : "IN")
           printf direction OFS

           printf global_subaccount

           printf "\n"

        }' FS=, OFS=, >"$global_out_path"
  else
    # A base string that will work with transfers that are only deposits but not yet withdrawals
    _header="info_eid,info_timestampms,info_type,info_currency,info_amount"

    lib_preprocess::test_header "info_destination" && _header+=",info_destination"
    lib_preprocess::test_header "info_txHash" && _header+=",info_txHash"

    # Off-chain withdrawal
    lib_preprocess::test_header "info_method" && _header+=",info_method"

    xan select "$_header" "$global_in_path" \
      | gawk -v global_year="$global_year" -v global_subaccount="$global_subaccount" -M -v PREC=100 \
        '{
           if (NR<2)
             next

           cmd = "date --utc \"+%F %T\" --date=@$(expr $(echo "$2"/1000 | bc))" | getline date

           if (date !~ global_year)
             next

           # NOTE: If info_method isnt ACH/Wire/SEN then it should be GUSD (exception being CreditCard)
           if ($8 == "ACH" || $8 == "Wire" || $8 == "SEN") {$4="USD"}
           else {gsub(/,USD,/,",GUSD,")}

           # Get/set info_method (if available)
           if ($8 == "ACH" || $8 == "Wire" || $8 == "SEN" || $8 == "CreditCard") {$6=$8}

           printf $1 OFS          # UID (info_eid)
           printf date OFS        # Date
           printf "TRANSFER" OFS  # Type
           printf $3 OFS          # OrderType (info_type)
           printf $4 OFS          # CurrencyOne (info_currency)
           printf OFS             # CurrencyTwo (N/A)

           # Amount
           direction=($5 ~ /^-/ ? "OUT" : "IN")
           sub(/^-/, "", $5)
           # TODO: getline an gawk remove-zeros script
           # NOTE: .18 will need PREC passed to gawk
           info_amount=sprintf("%.18f", $5)
           if (info_amount ~ /^[0-9]*\.[0-9]+/)
             {
               sub(/0+$/, "", info_amount)
               sub(/\.$/, "", info_amount)
               sub(/^\./, "0.", info_amount)
             }
           printf info_amount OFS

           printf OFS             # Price (N/A)
           printf OFS             # Fees (N/A)
           printf OFS             # Cost-basis (N/A)
           printf $6 OFS          # Destination (info_destination)
           printf $7 OFS          # TXID (info_txHash)

           printf direction OFS
           printf global_subaccount

           printf "\n"

        }' FS=, OFS=, >"$global_out_path"
  fi
}

function parse_trades()
{
  # TODO: Gemini bug? In rare cases, Gemini may include client_order_id ("echo back")
  # even though docker-finance never supplies an identifier nor place any orders.
  # In all cases, this column will be ignored but must also allow the header assertion to pass.
  local _client_order_id=""
  lib_preprocess::test_header "client_order_id" && _client_order_id=",client_order_id"

  lib_preprocess::assert_header "price,amount,timestamp,timestampms,type,aggressor,fee_currency,fee_amount,tid,order_id,exchange,is_auction_fill,is_clearing_fill,symbol${_client_order_id}"

  # Get symbol from base pair trade. Example: bchbtc-Trades.csv will return bch
  local _account_currency
  _account_currency="$(echo $global_in_filename | cut -d"-" -f1 | sed 's:...$::')"

  gawk -v account_currency="$_account_currency" \
    -v global_year="$global_year" -v global_subaccount="$global_subaccount" \
    '{
       if (NR<2)
         next

       cmd = "date --utc \"+%F %T\" --date=@$(expr $(echo "$4"/1000 | bc))" | getline date

       if (date !~ global_year)
         next

       printf $9 OFS                # UID (tid)
       printf date OFS              # Date
       printf "TRADE" OFS           # Type
       printf $5 OFS                # OrderType (type)
       printf toupper(account_currency) OFS  # CurrencyOne (account_currency)
       printf $7 OFS                # CurrencyTwo (fee_currency)

       # TODO: getline an gawk remove-zeros script
       amount=sprintf("%.8f", $2)
       if (amount ~ /^[0-9]*\.[0-9]+/)
         {
           sub(/0+$/, "", amount)
           sub(/\.$/, "", amount)
           sub(/^\./, "0.", amount)
         }
       printf amount OFS  # Amount

       cost=sprintf("%.8f", $1*$2)
       if (cost ~ /^[0-9]*\.[0-9]+/)
         {
           sub(/0+$/, "", cost)
           sub(/\.$/, "", cost)
           sub(/^\./, "0.", cost)
         }
       printf cost OFS  # Price  (will be used as a "total" for @@, not price)

       fee_amount=sprintf("%.8f", $8)
       if (fee_amount ~ /^[0-9]*\.[0-9]+/)
         {
           sub(/0+$/, "", fee_amount)
           sub(/\.$/, "", fee_amount)
           sub(/^\./, "0.", fee_amount)
         }
       printf fee_amount OFS  # Fees

       # TODO: HACK: see #51 and respective lib_taxes work-around
       # Calculate cost-basis
       switch ($5)
          {
            case "Buy":
              cost_basis=sprintf("%.8f", cost + fee_amount)
              break
            case "Sell":
              cost_basis=sprintf("%.8f", cost - fee_amount)
              break
            default:
              printf "FATAL: unsupported order type: " $5
              print $0
              exit
          }
       printf cost_basis OFS

       printf OFS      # Destination (N/A)
       printf $10 OFS  # TXID (tid)

       # Direction
       printf OFS

       printf global_subaccount

       printf "\n"

    }' FS=, OFS=, "$global_in_path" >"$global_out_path"
}

function parse_earn()
{
  lib_preprocess::assert_header "earnTransactionId,transactionId,transactionType,amountCurrency,amount,priceCurrency,priceAmount,dateTime"

  gawk -v global_year="$global_year" -v global_subaccount="$global_subaccount" -M -v PREC=100 \
    '{
       if (NR<2)
         next

       cmd = "date --utc \"+%F %T\" --date=@$(expr $(echo "$8"/1000 | bc))" | getline date

       if (date !~ global_year)
         next

       printf $1 OFS          # UID (earnTransactionId)
       printf date OFS        # Date
       printf "INTEREST" OFS  # Type
       printf "EARN" OFS      # OrderType (type)
       printf $4 OFS          # CurrencyOne (amountCurrency)
       printf OFS             # CurrencyTwo

       # Amount
       # TODO: getline an gawk remove-zeros script
       # NOTE: .22 requires PREC passed to gawk
       amount=sprintf("%.22f", $5)
       if (amount ~ /^[0-9]*\.[0-9]+/)
         {
           sub(/0+$/, "", amount)
           sub(/\.$/, "", amount)
           sub(/^\./, "0.", amount)
         }
       printf amount OFS

       # Price
       priceAmount=sprintf("%.22f", $7*$5)
       if (priceAmount ~ /^[0-9]*\.[0-9]+/)
         {
           sub(/0+$/, "", priceAmount)
           sub(/\.$/, "", priceAmount)
           sub(/^\./, "0.", priceAmount)
         }
       printf priceAmount OFS

       printf OFS  # Fees
       printf OFS  # Cost-basis
       printf OFS  # Destination
       printf OFS  # TXID

       # Direction
       printf "IN" OFS

       printf global_subaccount

       printf "\n";

    }' FS=, OFS=, "$global_in_path" >"$global_out_path"
}

function parse_card()
{
  lib_preprocess::assert_header "Reference Number,Transaction Post Date,Description of Transaction,Transaction Type,Amount"

  gawk -v global_year="$global_year" -v global_subaccount="$global_subaccount" \
    '{
       if (NR<2)
         next

       # Date format is MM/DD/YY
       yy=substr($2, 7, 2)
       global_yy=substr(global_year, 3, 2)

       if (yy != global_yy)
         next

       printf $1 OFS  # Reference Number
       printf $2 OFS  # Transaction Post Date
       printf $3 OFS  # Description of Transaction
       printf $4 OFS  # Transaction Type
       printf $5 OFS  # Amount

       direction=($5 ~ /^-/ ? "IN" : "OUT")
       printf direction OFS

       printf global_subaccount

       printf "\n";

    }' FS=, OFS=, "$global_in_path" >"$global_out_path"
}

function main()
{
  # Transfers
  lib_preprocess::test_header "info_eid" && parse_transfers && return 0

  # Trades (may come in either form)
  lib_preprocess::test_header "tid" && parse_trades && return 0
  lib_preprocess::test_header "info_tid" && parse_trades && return 0

  # Earn
  lib_preprocess::test_header "earnTransactionId" && parse_earn && return 0

  # Card
  lib_preprocess::test_header "Reference Number" && parse_card && return 0
}

main "$@"

# vim: sw=2 sts=2 si ai et
