module ActiveMerchant #:nodoc:
  module Billing #:nodoc:
    class PayboxDirectGateway < Gateway
      class_attribute :live_url_backup

      self.test_url   = 'https://preprod-ppps.paybox.com/PPPS.php'
      self.live_url   = 'https://ppps.paybox.com/PPPS.php'
      self.live_url_backup = 'https://ppps1.paybox.com/PPPS.php'

      # Payment API Version
      API_VERSION = '00103'

      # Transactions hash
      TRANSACTIONS = {
        authorization: '00001',
        capture: '00002',
        purchase: '00003',
        unreferenced_credit: '00004',
        void: '00005',
        refund: '00014'
      }

      CURRENCY_CODES = {
        'AUD' => '036',
        'CAD' => '124',
        'CZK' => '203',
        'DKK' => '208',
        'HKD' => '344',
        'ICK' => '352',
        'JPY' => '392',
        'NOK' => '578',
        'SGD' => '702',
        'SEK' => '752',
        'CHF' => '756',
        'GBP' => '826',
        'USD' => '840',
        'EUR' => '978',
        'XPF' => '953'
      }

      SUCCESS_CODES = ['00000']
      UNAVAILABILITY_CODES = %w[00001 00097 00098]
      SUCCESS_MESSAGE = 'The transaction was approved'
      FAILURE_MESSAGE = 'The transaction failed'

      # Money is referenced in cents
      self.money_format = :cents
      self.default_currency = 'EUR'

      # The countries the gateway supports merchants from as 2 digit ISO country codes
      self.supported_countries = ['FR']

      # The card types supported by the payment gateway
      self.supported_cardtypes = %i[visa master american_express diners_club jcb]

      # The homepage URL of the gateway
      self.homepage_url = 'http://www.paybox.com/'

      # The name of the gateway
      self.display_name = 'Paybox Direct'

      def initialize(options = {})
        requires!(options, :login, :password)
        super
      end

      def add_3dsecure(post, options)
        # ECI=02 => MasterCard success
        # ECI=05 => Visa, Amex or JCB success
        if options[:eci] == '02' || options[:eci] == '05'
          post[:"3DSTATUS"] = 'Y'
          post[:"3DENROLLED"] = 'Y'
          post[:"3DSIGNVAL"] = 'Y'
          post[:"3DERROR"] = '0'
        else
          post[:"3DSTATUS"] = 'N'
          post[:"3DENROLLED"] = 'N'
          post[:"3DSIGNVAL"] = 'N'
          post[:"3DERROR"] = '10000'
        end
        post[:"3DECI"] = options[:eci]
        post[:"3DXID"] = options[:xid]
        post[:"3DCAVV"] = options[:cavv]
        post[:"3DCAVVALGO"] = options[:cavv_algorithm]
      end

      def authorize(money, creditcard, options = {})
        post = {}
        add_invoice(post, options)
        add_creditcard(post, creditcard)
        add_3dsecure(post, options[:three_d_secure]) if options[:three_d_secure]
        add_amount(post, money, options)

        commit('authorization', money, post)
      end

      def purchase(money, creditcard, options = {})
        post = {}
        add_invoice(post, options)
        add_creditcard(post, creditcard)
        add_3dsecure(post, options[:three_d_secure]) if options[:three_d_secure]
        add_amount(post, money, options)

        commit('purchase', money, post)
      end

      def capture(money, authorization, options = {})
        requires!(options, :order_id)
        post = {}
        add_invoice(post, options)
        add_amount(post, money, options)
        post[:numappel] = authorization[0, 10]
        post[:numtrans] = authorization[10, 10]

        commit('capture', money, post)
      end

      def void(identification, options = {})
        requires!(options, :order_id, :amount)
        post = {}
        add_invoice(post, options)
        add_reference(post, identification)
        add_amount(post, options[:amount], options)
        post[:porteur] = '000000000000000'
        post[:dateval] = '0000'

        commit('void', options[:amount], post)
      end

      def credit(money, identification, options = {})
        ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
        refund(money, identification, options)
      end

      def refund(money, identification, options = {})
        post = {}
        add_invoice(post, options)
        add_reference(post, identification)
        add_amount(post, money, options)
        commit('refund', money, post)
      end

      private

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

      def add_creditcard(post, creditcard)
        post[:porteur] = creditcard.number
        post[:dateval] = expdate(creditcard)
        post[:cvv] = creditcard.verification_value if creditcard.verification_value?
      end

      def add_reference(post, identification)
        post[:numappel] = identification[0, 10]
        post[:numtrans] = identification[10, 10]
      end

      def add_amount(post, money, options)
        post[:montant] = ('0000000000' + (money ? amount(money) : ''))[-10..-1]
        post[:devise] = CURRENCY_CODES[options[:currency] || currency(money)]
      end

      def parse(body)
        results = {}
        body.split(/&/).each do |pair|
          key, val = pair.split(/\=/)
          results[key.downcase.to_sym] = CGI.unescape(val) if val
        end
        results
      end

      def commit(action, money = nil, parameters = nil)
        request_data = post_data(action, parameters)
        response = parse(ssl_post(test? ? self.test_url : self.live_url, request_data))
        response = parse(ssl_post(self.live_url_backup, request_data)) if service_unavailable?(response) && !test?
        Response.new(
          success?(response),
          message_from(response),
          response.merge(timestamp: parameters[:dateq]),
          test: test?,
          authorization: response[:numappel].to_s + response[:numtrans].to_s,
          fraud_review: false,
          sent_params: parameters.delete_if { |key, _value| %w[porteur dateval cvv].include?(key.to_s) }
        )
      end

      def success?(response)
        SUCCESS_CODES.include?(response[:codereponse])
      end

      def service_unavailable?(response)
        UNAVAILABILITY_CODES.include?(response[:codereponse])
      end

      def message_from(response)
        success?(response) ? SUCCESS_MESSAGE : (response[:commentaire] || FAILURE_MESSAGE)
      end

      def post_data(action, parameters = {})
        parameters.update(
          version: API_VERSION,
          type: TRANSACTIONS[action.to_sym],
          dateq: Time.now.strftime('%d%m%Y%H%M%S'),
          numquestion: unique_id(parameters[:order_id]),
          site: @options[:login].to_s[0, 7],
          rang: @options[:rang] || @options[:login].to_s[7..-1],
          cle: @options[:password],
          pays: '',
          archivage: parameters[:order_id]
        )

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

      def unique_id(seed = 0)
        randkey = "#{seed}#{Time.now.usec}".to_i % 2147483647 # Max paybox value for the question number

        "0000000000#{randkey}"[-10..-1]
      end
    end
  end
end
