require 'nokogiri'

module ActiveMerchant # :nodoc:
  module Billing # :nodoc:
    class FirstGivingGateway < Gateway
      self.test_url = 'http://usapisandbox.fgdev.net'
      self.live_url = 'https://api.firstgiving.com'

      self.supported_countries = ['US']
      self.supported_cardtypes = %i[visa master american_express discover]
      self.homepage_url = 'http://www.firstgiving.com/'
      self.default_currency = 'USD'
      self.display_name = 'FirstGiving'

      def initialize(options = {})
        requires!(options, :application_key, :security_token, :charity_id)
        super
      end

      def purchase(money, creditcard, options = {})
        post = {}
        add_invoice(post, options)
        add_creditcard(post, creditcard)
        add_address(post, options)
        add_customer_data(post, options)
        add_donation_data(post, money, options)
        commit('/donation/creditcard', post)
      end

      def refund(money, identifier, options = {})
        get = {}
        get[:transactionId] = identifier
        get[:tranType] = 'REFUNDREQUEST'
        commit('/transaction/refundrequest?' + encode(get))
      end

      private

      def add_donation_data(post, money, options)
        post[:amount] = amount(money)
        post[:charityId] = @options[:charity_id]
        post[:description] = (options[:description] || 'Purchase')
        post[:currencyCode] = (options[:currency] || currency(money))
      end

      def add_customer_data(post, options)
        post[:billToEmail] = (options[:email] || 'activemerchant@example.com')
        post[:remoteAddr]  = (options[:ip] || '127.0.0.1')
      end

      def add_address(post, options)
        if (billing_address = (options[:billing_address] || options[:address]))
          post[:billToAddressLine1]  = billing_address[:address1]
          post[:billToCity]          = billing_address[:city]
          post[:billToState]         = billing_address[:state]
          post[:billToZip]           = billing_address[:zip]
          post[:billToCountry]       = billing_address[:country]
        end
      end

      def add_invoice(post, options)
        post[:orderId] = options[:order_id]
      end

      def add_creditcard(post, creditcard)
        post[:billToFirstName]     = creditcard.first_name
        post[:billToLastName]      = creditcard.last_name
        post[:ccNumber]            = creditcard.number
        post[:ccType]              = creditcard_brand(creditcard.brand)
        post[:ccExpDateMonth]      = creditcard.month
        post[:ccExpDateYear]       = creditcard.year
        post[:ccCardValidationNum] = creditcard.verification_value
      end

      def parse(body)
        response = {}

        xml = Nokogiri::XML(body)
        element = xml.xpath('//firstGivingDonationApi/firstGivingResponse').first

        element.attributes.each do |name, attribute|
          response[name] = attribute.content
        end
        element.children.each do |child|
          next if child.text?

          response[child.name] = child.text
        end

        response
      end

      def commit(action, post = nil)
        url = (test? ? self.test_url : self.live_url) + action

        begin
          if post
            response = parse(ssl_post(url, post_data(post), headers))
          else
            response = parse(ssl_get(url, headers))
          end
        rescue ResponseError => e
          response = parse(e.response.body)
        end

        Response.new(
          (response['acknowledgement'] == 'Success'),
          (response['friendlyErrorMessage'] || response['verboseErrorMessage'] || response['acknowledgement']),
          response,
          authorization: response['transactionId'],
          test: test?
        )
      end

      def post_data(post)
        post.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
      end

      def encode(hash)
        hash.collect { |(k, v)| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join('&')
      end

      def creditcard_brand(brand)
        case brand
        when 'visa' then 'VI'
        when 'master' then 'MC'
        when 'discover' then 'DI'
        when 'american_express' then 'AX'
        else
          raise "Unhandled credit card brand #{brand}"
        end
      end

      def headers
        {
          'User-Agent'        => "ActiveMerchantBindings/#{ActiveMerchant::VERSION}",
          'JG_APPLICATIONKEY' => @options[:application_key].to_s,
          'JG_SECURITYTOKEN'  => @options[:security_token].to_s
        }
      end
    end
  end
end
