require 'socksify/http'

# Try to avoid runing multiple instances of this at same time, although it will probably work fine in most cases.
# Requires access to a TOR proxy and bitcoind needs to be running because Payout validation requests balance at order address.
# Similarly with litecoind if litecoin payments imported.
# The market should already have validated the payout address so it is not validated in the payout model.

# Recap: OrderPayouts exist on market and Payouts exist on payment server. They record basically the same information but the data structures are different.

# This connects to market server API to retrieve unpaid OrderPayouts that meet conditions such as a payout address has been set.
# In addition to the OrderPayout fields such as payment address and amount, we also retrieve some other information to do some validation and record keeping on payout server :
# order's btc_address, btc_price, OrderPayout username.
# Commission is retrieved as well because vendors may have different commission rates.

# OP records may be deleted on the market server by admin (in this unusual case) but if the payment server has already retrieved it, then the associated payout will remain on payment server.
# This only occurs when an expired order has its refund address set by the buyer, then the admin changes it to paid which automatically deletes the market OP record. The payment server
# may have already imported it. Therefore the admin is warned when setting orders to paid, that they should check the payment server first.

# How to prevent a hacked market sending bogus OrderPayouts? Some validation is done in the payout model which may detect fabricated OPs.
# The OP order_btc_address must be a known address generated by payout server and payouts are limited to the amount received to that address.
# The attacker could create a lot of OP using same order_btc_address but then the import won't work due to unique constraint on [order_btc_address, payout_type].
# The attacker could try using old paid orders and setting them as unpaid. But our payout server has a record of what has been paid already and will no allow a payout to be paid again.

# Validation of payout model can be bypassed by calling perform_now(:skip_order_address_validation)
# If bitcoind not running, this option will allow payouts to be created/updated but there are other reasons why
# this option may be useful even when bitcoind is running. See Payout model for details.
class ImportOrderPayoutsJob < ApplicationJob
  queue_as :default

  def perform(*args)
    skip_order_address_validation = false
    if args[0] == :skip_order_address_validation
      skip_order_address_validation = true
    end

    uri = URI.parse(Rails.configuration.admin_api_uri_base +
                    '/admin/orderpayouts/export')
    http = nil
    if Rails.env.production?
      tor_proxy_host = Rails.configuration.tor_proxy_host
      tor_proxy_port = Rails.configuration.tor_proxy_port
      http = Net::HTTP.SOCKSProxy(tor_proxy_host, tor_proxy_port).new(uri.host, uri.port)
    else
      http = Net::HTTP.new(uri.host, uri.port)
    end

    request = Net::HTTP::Post.new(uri.request_uri)
    request.content_type = 'application/json'
    request.body = { 'admin_api_key' => Rails.configuration.admin_api_key }.to_json
    response = http.request(request)
    order_payouts = JSON.parse response.body

    ScriptLog.info("retrieved #{order_payouts.size} OrderPayouts from market.")

    order_payouts.each do |op|
      ScriptLog.info("order_payout_id: #{op['order_payout_id']}, displayname: #{op['displayname']}, amount: #{op['payout_btc_amount']}, address_type: #{op['address_type']}, comm: #{op['commission']}")
      p = Payout.find_by order_payout_id: op['order_payout_id']
      if p
        # Need to update existing with both payout_btc_address and payout_btc_amount because user could change their mind on the address. Multiple payments made to an expired order
        # could result in payout_btc_amount updating.
        # payout_schedule can change. ie it could be scheduled for Sunday but then change to daily.
        # Users timezone could change.
        p.skip_order_address_validation = true if skip_order_address_validation
        p.assign_attributes(payout_btc_address: op['payout_btc_address'],
                            payout_btc_amount: op['payout_btc_amount'],
                            payout_schedule: op['payout_schedule'],
                            user_timezone: op['user_timezone'])
        # Want to only log when something has changed so not using update() method because it doesn't tell you if anything was updated.
        ScriptLog.info("payout id: #{p.id}, order_payout_id: #{op['order_payout_id']} updated.") if p.changed?
        if !p.save
          ScriptLog.warn("payout id: #{p.id} update failed.")
        end
        if p.paid
          # This implies market OrderPayout hasn't been updated yet because export doesn't include paid.
          # Just means that update_market_order_payouts.rb needs to be run.
          ScriptLog.warn("payout id: #{p.id} - already paid.")
        end
      else
        # Create new payout.
        if op['multipay'] == true
          # Multipay secondary orders have no payment made to their address which will cause model validation to fail when it checks balance received to address.
          # Multipay primary order does have a payment but can't tell if this is a primary or secondary order so skip both.
          skip_order_address_validation = true
        end
        if skip_order_address_validation
          p = Payout.new skip_order_address_validation: true
          ScriptLog.info("order_payout_id: #{op['order_payout_id']} skipping address validation.")
        else
          p = Payout.new
        end

        if Payout.where(payout_btc_address: op['payout_btc_address']).where.not(username: op['username']).count > 0
          ScriptLog.warn("Another user is also paying to this address. Will hold this payout.")
          # The job to process payments will ignore this until manually reviewed and hold is removed.
          p.hold = true
        end

        op.delete('multipay')  # Remove this key from the hash, doesn't need to be saved to db.
        p.update(op)
        if p.new_record?
          # Create failed. This could happen when the model validator checks the order's address has received enough btc to make the payment.
          ScriptLog.warn("Create payout validation failure on payout id: #{op['order_payout_id']}. Possibly not enough confirmations on their order payment.")
          p.errors.full_messages.each {|m| ScriptLog.warn m }
        end
      end
    end
  end
end
