module Killbill
  module Plugin
    module ActiveMerchant
      module ActiveRecord
        require 'active_record'

        class Transaction < ::ActiveRecord::Base

          extend ::Killbill::Plugin::ActiveMerchant::Helpers

          self.abstract_class = true

          @@quotes_cache = build_quotes_cache

          class << self

            def transactions_from_kb_payment_id(kb_payment_id, kb_tenant_id)
              where(:kb_payment_id => kb_payment_id, :kb_tenant_id => kb_tenant_id).order(:created_at)
            end

            [:authorize, :capture, :purchase, :credit, :refund].each do |transaction_type|
              define_method("#{transaction_type.to_s}s_from_kb_payment_id") do |kb_payment_id, kb_tenant_id|
                transaction_from_kb_payment_id(transaction_type.to_s.upcase, kb_payment_id, kb_tenant_id, :multiple)
              end
            end

            # For convenience
            alias_method :authorizations_from_kb_payment_id, :authorizes_from_kb_payment_id

            # For convenience
            def voids_from_kb_payment_id(kb_payment_id, kb_tenant_id)
              [void_from_kb_payment_id(kb_payment_id, kb_tenant_id)]
            end

            # void is special: unique void per payment_id
            def void_from_kb_payment_id(kb_payment_id, kb_tenant_id)
              transaction_from_kb_payment_id(:VOID, kb_payment_id, kb_tenant_id, :single)
            end

            def from_kb_payment_transaction_id(kb_payment_transaction_id, kb_tenant_id)
              transaction_from_kb_payment_transaction_id(nil, kb_payment_transaction_id, kb_tenant_id, :single)
            end

            def find_candidate_transaction_for_refund(kb_payment_id, kb_tenant_id, amount_in_cents)
              begin
                do_find_candidate_transaction_for_refund(:authorize, kb_payment_id, kb_tenant_id, amount_in_cents)
              rescue
                do_find_candidate_transaction_for_refund(:purchase, kb_payment_id, kb_tenant_id, amount_in_cents)
              end
            end

            private

            def do_find_candidate_transaction_for_refund(api_call, kb_payment_id, kb_tenant_id, amount_in_cents)
              # Find one successful charge which amount is at least the amount we are trying to refund
              if kb_tenant_id.nil?
                transactions = where("amount_in_cents >= #{@@quotes_cache[amount_in_cents]} AND api_call = #{@@quotes_cache[api_call]} AND kb_tenant_id is NULL AND kb_payment_id = #{@@quotes_cache[kb_payment_id]}").order(:created_at)
              else
                transactions = where("amount_in_cents >= #{@@quotes_cache[amount_in_cents]} AND api_call = #{@@quotes_cache[api_call]} AND kb_tenant_id = #{@@quotes_cache[kb_tenant_id]} AND kb_payment_id = #{@@quotes_cache[kb_payment_id]}").order(:created_at)
              end
              raise "Unable to find transaction for payment #{kb_payment_id} and api_call #{api_call}" if transactions.size == 0

              # We have candidates, but we now need to make sure we didn't refund more than for the specified amount
              amount_refunded_in_cents = where("api_call = #{@@quotes_cache['refund']} and kb_payment_id = #{@@quotes_cache[kb_payment_id]}").sum('amount_in_cents')

              amount_left_to_refund_in_cents = -amount_refunded_in_cents
              transactions.map { |transaction| amount_left_to_refund_in_cents += transaction.amount_in_cents }
              raise "Amount #{amount_in_cents} too large to refund for payment #{kb_payment_id}" if amount_left_to_refund_in_cents < amount_in_cents

              transactions.first
            end

            [:kb_payment_id, :kb_payment_transaction_id].each do |attribute|
              define_method("transaction_from_#{attribute.to_s}") do |transaction_type, attribute_value, kb_tenant_id, how_many|
                if kb_tenant_id.nil?
                  if transaction_type.nil?
                    transactions = where("kb_tenant_id is NULL AND #{attribute.to_s} = ?", attribute_value).order(:created_at)
                  else
                    transactions = where("transaction_type = #{@@quotes_cache[transaction_type]} AND kb_tenant_id is NULL AND #{attribute.to_s} = #{@@quotes_cache[attribute_value]}").order(:created_at)
                  end
                else
                  if transaction_type.nil?
                    transactions = where("kb_tenant_id = #{@@quotes_cache[kb_tenant_id]} AND #{attribute.to_s} = #{@@quotes_cache[attribute_value]}").order(:created_at)
                  else
                    transactions = where("transaction_type = #{@@quotes_cache[transaction_type]} AND kb_tenant_id = #{@@quotes_cache[kb_tenant_id]} AND #{attribute.to_s} = #{@@quotes_cache[attribute_value]}").order(:created_at)
                  end
                end
                if how_many == :single
                  raise "Kill Bill #{attribute} = #{attribute_value} mapping to multiple plugin transactions" if transactions.size > 1
                  transactions[0]
                else
                  transactions
                end
              end
            end
          end
        end
      end
    end
  end
end