module Spree
  class Gateway::Beanstream < Gateway
    preference :login, :string
    preference :user, :string
    preference :password, :string
    preference :secure_profile_api_key, :string

    def provider_class
      ActiveMerchant::Billing::BeanstreamGateway
    end

    def payment_profiles_supported?
      true
    end

    def create_profile(payment)
      creditcard = payment.source
      if creditcard.gateway_customer_profile_id.nil?
        options = options_for_create_customer_profile(creditcard, {})
        verify_creditcard_name!(creditcard)
        result = provider.store(creditcard, options)
        if result.success?
          creditcard.update_attributes(:gateway_customer_profile_id => result.params['customerCode'], :gateway_payment_profile_id => result.params['customer_vault_id'])
        else
          payment.send(:gateway_error, result)
        end
      end
    end

    def capture(transaction, creditcard, gateway_options)
      beanstream_gateway.capture((transaction.amount*100).round, transaction.response_code, gateway_options)
    end

    def void(transaction_response, creditcard, gateway_options)
      beanstream_gateway.void(transaction_response, gateway_options)
    end

    def credit(amount, creditcard, response_code, gateway_options = {})
      amount = (amount * -1) if amount < 0
      beanstream_gateway.credit(amount, response_code, gateway_options)
    end

    private
      def beanstream_gateway
        ActiveMerchant::Billing::Base.gateway_mode = preferred_server.to_sym
        gateway_options = options
        ActiveMerchant::Billing::BeanstreamGateway.new(gateway_options)
      end

      def verify_creditcard_name!(creditcard)
        bill_address = creditcard.payments.first.order.bill_address
        creditcard.first_name = bill_address.firstname unless creditcard.first_name?
        creditcard.last_name = bill_address.lastname   unless creditcard.last_name?
      end

      def options_for_create_customer_profile(creditcard, gateway_options)
        order = creditcard.payments.first.order
        address = order.bill_address
        { :email=>order.email,
          :billing_address=>
          { :name=>address.full_name,
            :phone=>address.phone,
            :address1=>address.address1,
            :address2=>address.address2,
            :city=>address.city,
            :state=>address.state_name || address.state.abbr,
            :country=>address.country.iso,
            :zip=>address.zipcode
            }
          }.merge(gateway_options)
      end

      SECURE_PROFILE_URL = 'https://www.beanstream.com/scripts/payment_profile.asp'
      SP_SERVICE_VERSION = '1.1'
      PROFILE_OPERATIONS = { :new => 'N', :modify => 'M' }

      ActiveMerchant::Billing::BeanstreamGateway.class_eval do

        def store(credit_card, options = {})
          post = {}
          add_address(post, options)
          add_credit_card(post, credit_card)
          add_secure_profile_variables(post,options)
          commit(post, true)
        end

        #can't actually delete a secure profile with the supplicaed API. This function sets the status of the profile to closed (C).
        #Closed profiles will have to removed manually.
        def delete(vault_id)
          update(vault_id, false, { :status => 'C' })
        end

        #alias_method :unstore, :delete

        # Update the values (such as CC expiration) stored at
        # the gateway.  The CC number must be supplied in the
        # CreditCard object.
        def update(vault_id, credit_card, options = {})
          post = {}
          add_address(post, options)
          add_credit_card(post, credit_card)
          options.merge!({ :vault_id => vault_id, :operation => secure_profile_action(:modify) })
          add_secure_profile_variables(post,options)
          commit(post, true)
        end

        # CORE #

        def secure_profile_action(type)
          PROFILE_OPERATIONS[type] || PROFILE_OPERATIONS[:new]
        end

        def add_credit_card(post, credit_card)
          if credit_card
            if credit_card.has_payment_profile?
              post[:customerCode] = credit_card.gateway_customer_profile_id
            else
              post[:trnCardOwner] = credit_card.name
              post[:trnCardNumber] = credit_card.number
              post[:trnExpMonth] = format(credit_card.month, :two_digits)
              post[:trnExpYear] = format(credit_card.year, :two_digits)
              post[:trnCardCvd] = credit_card.verification_value
            end
          end
        end

        def add_secure_profile_variables(post, options = {})
          post[:serviceVersion] = SP_SERVICE_VERSION
          post[:responseFormat] = 'QS'
          post[:cardValidation] = (options[:cardValidation].to_i == 1) || '0'

          post[:operationType] = options[:operationType] || options[:operation] || secure_profile_action(:new)
          post[:customerCode] = options[:billing_id] || options[:vault_id] || false
          post[:status] = options[:status]
        end

        def commit(params, use_profile_api = false)
          post(post_data(params,use_profile_api),use_profile_api)
        end

        def post(data, use_profile_api)
          response = parse(ssl_post((use_profile_api ? SECURE_PROFILE_URL : ActiveMerchant::Billing::BeanstreamGateway::URL), data))
          if response[:responseCode].eql?("17") # Found matching card
            response[:responseCode] = "1"
            response[:customerCode] = response[:matchedCustomerCode]
          end
          response[:customer_vault_id] = response[:customerCode] if response[:customerCode]
          build_response(success?(response), message_from(response), response,
                         :test => test? || response[:authCode] == 'TEST',
                         :authorization => authorization_from(response),
                         :cvv_result => ActiveMerchant::Billing::BeanstreamGateway::CVD_CODES[response[:cvdId]],
                         :avs_result => { :code => (ActiveMerchant::Billing::BeanstreamGateway::AVS_CODES.include? response[:avsId]) ? ActiveMerchant::Billing::BeanstreamGateway::AVS_CODES[response[:avsId]] : response[:avsId] }
                         )
        end

        def message_from(response)
          response[:messageText] || response[:responseMessage]
        end

        def success?(response)
          response[:responseType] == 'R' || response[:trnApproved] == '1' || response[:responseCode] == '1'
        end

        def add_source(post, source)
          if source.is_a?(String) or source.is_a?(Integer)
            post[:customerCode] = source
          else
            if source.respond_to?(:type) && source.type.to_s == 'check'
              add_check(post, source)
            else
              add_credit_card(post, source)
            end
          end
        end

        def post_data(params, use_profile_api)
          params[:requestType] = 'BACKEND'
          if use_profile_api
            params[:merchantId] = @options[:login]
            params[:passCode] = @options[:secure_profile_api_key]
          else
            params[:username] = @options[:user] if @options[:user]
            params[:password] = @options[:password] if @options[:password]
            params[:merchant_id] = @options[:login]
          end
          params[:vbvEnabled] = '0'
          params[:scEnabled] = '0'

          params.reject { |k, v| v.blank? }.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
        end
      end
  end
end
