require File.dirname(__FILE__) + '/braintree/braintree_common'

begin
  require "braintree"
rescue LoadError
  raise "Could not load the braintree gem.  Use `gem install braintree` to install it."
end

raise "Need braintree gem 2.x.y. Run `gem install braintree --version '~>2.0'` to get the correct version." unless Braintree::Version::Major == 2

module ActiveMerchant #:nodoc:
  module Billing #:nodoc:
    class BraintreeBlueGateway < Gateway
      include BraintreeCommon
      
      self.display_name = 'Braintree (Blue Platform)'

      def initialize(options = {})
        requires!(options, :merchant_id, :public_key, :private_key)
        @options = options
        Braintree::Configuration.merchant_id = options[:merchant_id]
        Braintree::Configuration.public_key = options[:public_key]
        Braintree::Configuration.private_key = options[:private_key]
        Braintree::Configuration.environment = test? ? :sandbox : :production
        Braintree::Configuration.logger.level = Logger::ERROR if Braintree::Configuration.logger
        Braintree::Configuration.custom_user_agent = "ActiveMerchant #{ActiveMerchant::VERSION}"
        super
      end

      def authorize(money, credit_card_or_vault_id, options = {})
        create_transaction(:sale, money, credit_card_or_vault_id, options)
      end

      def capture(money, authorization, options = {})
        commit do
          result = Braintree::Transaction.submit_for_settlement(authorization, amount(money).to_s)
          Response.new(result.success?, message_from_result(result))
        end
      end

      def purchase(money, credit_card_or_vault_id, options = {})
        authorize(money, credit_card_or_vault_id, options.merge(:submit_for_settlement => true))
      end

      def credit(money, credit_card_or_vault_id, options = {})
        create_transaction(:credit, money, credit_card_or_vault_id, options)
      end

      def refund(transaction_id, options = {})
        commit do
          result = Braintree::Transaction.find(transaction_id).refund
          Response.new(result.success?, message_from_result(result))
        end
      end

      def void(authorization, options = {})
        commit do
          result = Braintree::Transaction.void(authorization)
          Response.new(result.success?, message_from_result(result),
            :braintree_transaction => (result.transaction if result.success?)
          )
        end
      end

      def store(creditcard, options = {})
        commit do
          result = Braintree::Customer.create(
            :first_name => creditcard.first_name,
            :last_name => creditcard.last_name,
            :email => options[:email],
            :credit_card => {
              :number => creditcard.number,
              :cvv => creditcard.verification_value,
              :expiration_month => creditcard.month.to_s.rjust(2, "0"),
              :expiration_year => creditcard.year.to_s
            }
          )
          Response.new(result.success?, message_from_result(result),
            {
              :braintree_customer => (result.customer if result.success?),
              :customer_vault_id => (result.customer.id if result.success?)
            }
          )
        end
      end

      def update(vault_id, creditcard, options = {})
        braintree_credit_card = nil
        customer_update_result = commit do
          braintree_credit_card = Braintree::Customer.find(vault_id).credit_cards.detect { |cc| cc.default? }
          return Response.new(false, 'Braintree::NotFoundError') if braintree_credit_card.nil?
          result = Braintree::Customer.update(vault_id,
            :first_name => creditcard.first_name,
            :last_name => creditcard.last_name,
            :email => options[:email]
          )
          Response.new(result.success?, message_from_result(result),
            :braintree_customer => (Braintree::Customer.find(vault_id) if result.success?)
          )
        end
        return customer_update_result unless customer_update_result.success?
        credit_card_update_result = commit do
          result = Braintree::CreditCard.update(braintree_credit_card.token,
              :number => creditcard.number,
              :expiration_month => creditcard.month.to_s.rjust(2, "0"),
              :expiration_year => creditcard.year.to_s
          )
          Response.new(result.success?, message_from_result(result),
            :braintree_customer => (Braintree::Customer.find(vault_id) if result.success?)
          )
        end
      end

      def unstore(customer_vault_id)
        commit do
          Braintree::Customer.delete(customer_vault_id)
          Response.new(true, "OK")
        end
      end
      alias_method :delete, :unstore

      private

      def map_address(address)
        return {} if address.nil?
        {
          :street_address => address[:address1],
          :extended_address => address[:address2],
          :company => address[:company],
          :locality => address[:city],
          :region => address[:state],
          :postal_code => address[:zip],
          :country_name => address[:country]
        }
      end

      def commit(&block)
        yield
      rescue Braintree::BraintreeError => ex
        Response.new(false, ex.class.to_s)
      end

      def message_from_result(result)
        if result.success?
          "OK"
        else
          result.errors.map { |e| "#{e.message} (#{e.code})" }.join(" ")
        end
      end

      def create_transaction(transaction_type, money, credit_card_or_vault_id, options)
        parameters = {
          :amount => amount(money).to_s,
          :order_id => options[:order_id],
          :customer => {
            :id => options[:store] == true ? "" : options[:store],
            :email => options[:email]
          },
          :options => {
            :store_in_vault => options[:store] ? true : false,
            :submit_for_settlement => options[:submit_for_settlement]
          }
        }
        if credit_card_or_vault_id.is_a?(String) || credit_card_or_vault_id.is_a?(Integer)
          parameters[:customer_id] = credit_card_or_vault_id
        else
          parameters[:customer].merge!(
            :first_name => credit_card_or_vault_id.first_name,
            :last_name => credit_card_or_vault_id.last_name
          )
          parameters[:credit_card] = {
            :number => credit_card_or_vault_id.number,
            :cvv => credit_card_or_vault_id.verification_value,
            :expiration_month => credit_card_or_vault_id.month.to_s.rjust(2, "0"),
            :expiration_year => credit_card_or_vault_id.year.to_s
          }
        end
        parameters[:billing] = map_address(options[:billing_address]) if options[:billing_address]
        parameters[:shipping] = map_address(options[:shipping_address]) if options[:shipping_address]
        commit do
          result = Braintree::Transaction.send(transaction_type, parameters)
          response_params, response_options, avs_result, cvv_result = {}, {}, {}, {}
          if result.success?
            response_params[:braintree_transaction] = result.transaction
            response_params[:customer_vault_id] = result.transaction.customer_details.id
            response_options[:authorization] = result.transaction.id
          end
          if result.transaction
            avs_result = {
              'code' => '', 'message' => '',
              'street_match' => result.transaction.avs_street_address_response_code == 'M',
              'postal_match' => result.transaction.avs_postal_code_response_code == 'M'
            }
            cvv_result = {
              'code' => result.transaction.cvv_response_code, 'message' => ''
            }
            message = result.transaction.processor_response_code + " " + result.transaction.processor_response_text
          else
            message = message_from_result(result)
          end
          response = Response.new(result.success?, message, response_params, response_options)
          response.instance_variable_set("@avs_result", avs_result)
          response.instance_variable_set("@cvv_result", cvv_result)
          response
        end
      end
    end
  end
end