module ActiveMerchant #:nodoc:
  module Billing #:nodoc:
    class DibsGateway < Gateway
      self.display_name = 'DIBS'
      self.homepage_url = 'http://www.dibspayment.com/'

      self.live_url = 'https://api.dibspayment.com/merchant/v1/JSON/Transaction/'

      self.supported_countries = %w[US FI NO SE GB]
      self.default_currency = 'USD'
      self.money_format = :cents
      self.supported_cardtypes = %i[visa master american_express discover]

      def initialize(options = {})
        requires!(options, :merchant_id, :secret_key)
        super
      end

      def purchase(amount, payment_method, options = {})
        MultiResponse.run(false) do |r|
          r.process { authorize(amount, payment_method, options) }
          r.process { capture(amount, r.authorization, options) }
        end
      end

      def authorize(amount, payment_method, options = {})
        post = {}
        add_amount(post, amount)
        add_invoice(post, amount, options)
        if payment_method.respond_to?(:number)
          add_payment_method(post, payment_method, options)
          commit(:authorize, post)
        else
          add_ticket_id(post, payment_method)
          commit(:authorize_ticket, post)
        end
      end

      def capture(amount, authorization, options = {})
        post = {}
        add_amount(post, amount)
        add_reference(post, authorization)

        commit(:capture, post)
      end

      def void(authorization, options = {})
        post = {}
        add_reference(post, authorization)

        commit(:void, post)
      end

      def refund(amount, authorization, options = {})
        post = {}
        add_amount(post, amount)
        add_reference(post, authorization)

        commit(:refund, post)
      end

      def verify(credit_card, options = {})
        MultiResponse.run(:use_first_response) do |r|
          r.process { authorize(100, credit_card, options) }
          r.process(:ignore_result) { void(r.authorization, options) }
        end
      end

      def store(payment_method, options = {})
        post = {}

        add_invoice(post, 0, options)
        add_payment_method(post, payment_method, options)

        commit(:store, post)
      end

      def supports_scrubbing?
        true
      end

      def scrub(transcript)
        transcript.
          gsub(%r(("cardNumber\\?":\\?")[^"]*)i, '\1[FILTERED]').
          gsub(%r(("cvc\\?":\\?")[^"]*)i, '\1[FILTERED]')
      end

      private

      CURRENCY_CODES = Hash.new { |_h, k| raise ArgumentError.new("Unsupported currency: #{k}") }
      CURRENCY_CODES['USD'] = '840'
      CURRENCY_CODES['DKK'] = '208'
      CURRENCY_CODES['NOK'] = '578'
      CURRENCY_CODES['SEK'] = '752'
      CURRENCY_CODES['GBP'] = '826'
      CURRENCY_CODES['EUR'] = '978'

      def add_invoice(post, money, options)
        post[:orderId] = options[:order_id] || generate_unique_id
        post[:currency] = CURRENCY_CODES[options[:currency] || currency(money)]
      end

      def add_ticket_id(post, payment_method)
        post[:ticketId] = payment_method
      end

      def add_payment_method(post, payment_method, options)
        post[:cardNumber] = payment_method.number
        post[:cvc] = payment_method.verification_value if payment_method.verification_value
        post[:expYear] = format(payment_method.year, :two_digits)
        post[:expMonth] = payment_method.month
        post[:clientIp] = options[:ip] || '127.0.0.1'
        post[:test] = true if test?
      end

      def add_reference(post, authorization)
        post[:transactionId] = authorization
      end

      def add_amount(post, amount)
        post[:amount] = amount
      end

      ACTIONS = {
        authorize: 'AuthorizeCard',
        authorize_ticket: 'AuthorizeTicket',
        capture: 'CaptureTransaction',
        void: 'CancelTransaction',
        refund: 'RefundTransaction',
        store: 'CreateTicket'
      }

      def commit(action, post)
        post[:merchantId] = @options[:merchant_id]

        data = build_request(post)
        raw = parse(ssl_post(url(action), "request=#{data}", headers))
        succeeded = success_from(raw)
        Response.new(
          succeeded,
          message_from(succeeded, raw),
          raw,
          authorization: authorization_from(post, raw),
          test: test?
        )
      rescue JSON::ParserError
        unparsable_response(raw)
      end

      def headers
        {
          'Content-Type' => 'application/x-www-form-urlencoded'
        }
      end

      def build_request(post)
        add_hmac(post)
        post.to_json
      end

      def add_hmac(post)
        data = post.sort.collect { |key, value| "#{key}=#{value}" }.join('&')
        digest = OpenSSL::Digest.new('sha256')
        key = [@options[:secret_key]].pack('H*')
        post[:MAC] = OpenSSL::HMAC.hexdigest(digest, key, data)
      end

      def url(action)
        live_url + ACTIONS[action]
      end

      def parse(body)
        JSON.parse(body)
      end

      def success_from(raw_response)
        raw_response['status'] == 'ACCEPT'
      end

      def message_from(succeeded, response)
        if succeeded
          'Succeeded'
        else
          response['status'] + ': ' + response['declineReason'] || 'Unable to read error message'
        end
      end

      def authorization_from(request, response)
        response['transactionId'] || response['ticketId'] || request[:transactionId]
      end

      def unparsable_response(raw_response)
        message = 'Invalid JSON response received from Dibs. Please contact Dibs if you continue to receive this message.'
        message += " (The raw response returned by the API was #{raw_response.inspect})"
        return Response.new(false, message)
      end
    end
  end
end
