module ActiveMerchant #:nodoc:
  module Billing #:nodoc:
    class SamuraiGateway < Gateway

      self.homepage_url = 'https://samurai.feefighters.com'
      self.display_name = 'Samurai'
      self.supported_countries = ['US']
      self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club]
      self.default_currency = 'USD'
      self.money_format = :dollars

      def initialize(options = {})
        begin
          require 'samurai'
        rescue LoadError
          raise "Could not load the samurai gem (>= 0.2.25).  Use `gem install samurai` to install it."
        end

        requires!(options, :login, :password, :processor_token)
        Samurai.options = {
          :merchant_key       => options[:login],
          :merchant_password  => options[:password],
          :processor_token    => options[:processor_token]
        }

        super
      end

      def authorize(money, credit_card_or_vault_id, options = {})
        token = payment_method_token(credit_card_or_vault_id, options)
        return token if token.is_a?(Response)

        authorize = Samurai::Processor.authorize(token, amount(money), processor_options(options))
        handle_result(authorize)
      end

      def purchase(money, credit_card_or_vault_id, options = {})
        token = payment_method_token(credit_card_or_vault_id, options)
        return token if token.is_a?(Response)

        purchase = Samurai::Processor.purchase(token, amount(money), processor_options(options))
        handle_result(purchase)
      end

      def capture(money, authorization_id, options = {})
        transaction = Samurai::Transaction.find(authorization_id)
        handle_result(transaction.capture(amount(money)))
      end

      def refund(money, transaction_id, options = {})
        transaction = Samurai::Transaction.find(transaction_id)
        handle_result(transaction.credit(amount(money)))
      end

      def void(transaction_id, options = {})
        transaction = Samurai::Transaction.find(transaction_id)
        handle_result(transaction.void)
      end

      def store(creditcard, options = {})
        address = options[:billing_address] || options[:address] || {}

        result = Samurai::PaymentMethod.create({
          :card_number  => creditcard.number,
          :expiry_month => creditcard.month.to_s.rjust(2, "0"),
          :expiry_year  => creditcard.year.to_s,
          :cvv          => creditcard.verification_value,
          :first_name   => creditcard.first_name,
          :last_name    => creditcard.last_name,
          :address_1    => address[:address1],
          :address_2    => address[:address2],
          :city         => address[:city],
          :zip          => address[:zip],
          :sandbox      => test?
        })
        result.retain if options[:retain] && result.is_sensitive_data_valid && result.payment_method_token

        Response.new(result.is_sensitive_data_valid,
                     message_from_result(result),
                     { :payment_method_token => result.is_sensitive_data_valid && result.payment_method_token })
      end

      private

      def payment_method_token(credit_card_or_vault_id, options)
        return credit_card_or_vault_id if credit_card_or_vault_id.is_a?(String)
        store_result = store(credit_card_or_vault_id, options)
        store_result.success? ? store_result.params["payment_method_token"] : store_result
      end

      def handle_result(result)
        response_params, response_options = {}, {}
        if result.success?
          response_options[:test] = test?
          response_options[:authorization] = result.reference_id
          response_params[:reference_id] = result.reference_id
          response_params[:transaction_token] = result.transaction_token
          response_params[:payment_method_token] = result.payment_method.payment_method_token
        end

        response_options[:avs_result] = { :code => result.processor_response && result.processor_response.avs_result_code }
        response_options[:cvv_result] = result.processor_response && result.processor_response.cvv_result_code

        message = message_from_result(result)
        Response.new(result.success?, message, response_params, response_options)
      end

      def message_from_result(result)
        return "OK" if result.success?
        result.errors.map {|_, messages| messages }.join(" ")
      end

      def processor_options(options)
        options.slice(:billing_reference, :customer_reference, :custom, :descriptor)
      end
    end
  end
end
