require 'digest/md5'

module ActiveMerchant # :nodoc:
  module Billing # :nodoc:
    class NetaxeptGateway < Gateway
      self.test_url = 'https://epayment-test.bbs.no/'
      self.live_url = 'https://epayment.bbs.no/'

      # The countries the gateway supports merchants from as 2 digit ISO country codes
      self.supported_countries = %w[NO DK SE FI]

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

      # The homepage URL of the gateway
      self.homepage_url = 'http://www.betalingsterminal.no/Netthandel-forside/'

      # The name of the gateway
      self.display_name = 'BBS Netaxept'

      self.money_format = :cents

      self.default_currency = 'NOK'

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

      def purchase(money, creditcard, options = {})
        requires!(options, :order_id)

        MultiResponse.run do |r|
          r.process { authorize(money, creditcard, options) }
          r.process { capture(money, r.authorization, options) }
        end
      end

      def authorize(money, creditcard, options = {})
        requires!(options, :order_id)

        MultiResponse.run do |r|
          r.process { setup_transaction(money, options) }
          r.process { add_and_auth_credit_card(r.authorization, creditcard, options) }
          r.process { query_transaction(r.authorization, options) }
        end
      end

      def capture(money, authorization, options = {})
        post = {}
        add_credentials(post, options)
        add_authorization(post, authorization, money)
        post[:operation] = 'Capture'
        commit('Netaxept/process.aspx', post)
      end

      def refund(money, authorization, options = {})
        post = {}
        add_credentials(post, options)
        add_authorization(post, authorization, money)
        post[:operation] = 'Credit'
        commit('Netaxept/process.aspx', post)
      end

      def void(authorization, options = {})
        post = {}
        add_credentials(post, options)
        add_authorization(post, authorization)
        post[:operation] = 'Annul'
        commit('Netaxept/process.aspx', post)
      end

      private

      def setup_transaction(money, options)
        post = {}
        add_credentials(post, options)
        add_order(post, money, options)
        commit('Netaxept/Register.aspx', post)
      end

      def add_and_auth_credit_card(authorization, creditcard, options)
        post = {}
        add_credentials(post, options, false)
        add_authorization(post, authorization)
        add_creditcard(post, creditcard)
        commit('terminal/default.aspx', post, false)
      end

      def query_transaction(authorization, options)
        post = {}
        add_credentials(post, options)
        add_authorization(post, authorization)
        commit('Netaxept/query.aspx', post)
      end

      def add_credentials(post, options, secure = true)
        post[:merchantId] = @options[:login]
        post[:token] = @options[:password] if secure
      end

      def add_authorization(post, authorization, money = nil)
        post[:transactionId] = authorization
        post[:transactionAmount] = amount(money) if money
      end

      def add_order(post, money, options)
        post[:serviceType] = 'M'
        post[:orderNumber] = options[:order_id]
        post[:amount] = amount(money)
        post[:currencyCode] = (options[:currency] || currency(money))
        post[:autoAuth] = 'true'
      end

      def add_creditcard(post, options)
        post[:pan] = options.number
        post[:expiryDate] = format(options.month, :two_digits) + format(options.year, :two_digits)
        post[:securityCode] = options.verification_value
      end

      def commit(path, parameters, xml = true)
        raw = parse(ssl_get(build_url(path, parameters)), xml)

        success = false
        authorization = (raw['TransactionId'] || parameters[:transactionId])
        if /Exception|Error/.match?(raw[:container])
          message = (raw['Message'] || raw['Error']['Message'])
        elsif raw['Error'] && !raw['Error'].empty?
          message = (raw['Error']['ResponseText'] || raw['Error']['ResponseCode'])
        else
          message = (raw['ResponseText'] || raw['ResponseCode'] || 'OK')
          success = true
        end

        Response.new(
          success,
          message,
          raw,
          test: test?,
          authorization:
        )
      end

      def parse(result, expects_xml = true)
        if expects_xml
          doc = REXML::Document.new(result)
          extract_xml(doc.root).merge(container: doc.root.name)
        else
          { result: }
        end
      end

      def extract_xml(element)
        if element.has_elements?
          hash = {}
          element.elements.each do |e|
            hash[e.name] = extract_xml(e)
          end
          hash
        else
          element.text
        end
      end

      def build_url(base, parameters = nil)
        url = (test? ? self.test_url : self.live_url).dup
        url << base
        if parameters
          url << '?'
          url << encode(parameters)
        end
        url
      end

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