require 'nokogiri'

module ActiveMerchant # :nodoc:
  module Billing # :nodoc:
    class WorldpayUsGateway < Gateway
      class_attribute :backup_url

      self.display_name = 'Worldpay US'
      self.homepage_url = 'http://www.worldpay.com/us'

      # No sandbox, just use test cards.
      self.live_url   = 'https://trans.worldpay.us/cgi-bin/process.cgi'
      self.backup_url = 'https://trans.gwtx01.com/cgi-bin/process.cgi'

      self.supported_countries = ['US']
      self.default_currency = 'USD'
      self.money_format = :dollars
      self.supported_cardtypes = %i[visa master american_express discover jcb]

      def initialize(options = {})
        requires!(options, :acctid, :subid, :merchantpin)
        super
      end

      def purchase(money, payment_method, options = {})
        post = {}
        add_invoice(post, money, options)
        add_payment_method(post, payment_method)
        add_customer_data(post, options)

        commit('purchase', options, post)
      end

      def authorize(money, payment, options = {})
        post = {}
        add_invoice(post, money, options)
        add_credit_card(post, payment)
        add_customer_data(post, options)

        commit('authorize', options, post)
      end

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

        commit('capture', options, post)
      end

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

        commit('refund', options, post)
      end

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

        commit('void', options, 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 supports_scrubbing?
        true
      end

      def scrub(transcript)
        transcript.
          gsub(%r((&?merchantpin=)[^&]*)i, '\1[FILTERED]').
          gsub(%r((&?ccnum=)[^&]*)i, '\1[FILTERED]').
          gsub(%r((&?ckacct=)[^&]*)i, '\1[FILTERED]').
          gsub(%r((&?cvv2=)[^&]*)i, '\1[FILTERED]')
      end

      private

      def url(options)
        options[:use_backup_url].to_s == 'true' ? self.backup_url : self.live_url
      end

      def add_customer_data(post, options)
        if (billing_address = (options[:billing_address] || options[:address]))
          post[:ci_companyname] = billing_address[:company]
          post[:ci_billaddr1]   = billing_address[:address1]
          post[:ci_billaddr2]   = billing_address[:address2]
          post[:ci_billcity]    = billing_address[:city]
          post[:ci_billstate]   = billing_address[:state]
          post[:ci_billzip]     = billing_address[:zip]
          post[:ci_billcountry] = billing_address[:country]

          post[:ci_phone]       = billing_address[:phone]
          post[:ci_email]       = billing_address[:email]
          post[:ci_ipaddress]   = billing_address[:ip]
        end

        if (shipping_address = options[:shipping_address])
          post[:ci_shipaddr1] = shipping_address[:address1]
          post[:ci_shipaddr2] = shipping_address[:address2]
          post[:ci_shipcity] = shipping_address[:city]
          post[:ci_shipstate] = shipping_address[:state]
          post[:ci_shipzip] = shipping_address[:zip]
          post[:ci_shipcountry] = shipping_address[:country]
        end
      end

      def add_invoice(post, money, options)
        post[:amount] = amount(money)
        post[:currencycode] = (options[:currency] || currency(money))
        post[:merchantordernumber] = options[:order_id] if options[:order_id]
      end

      def add_payment_method(post, payment_method)
        if card_brand(payment_method) == 'check'
          add_check(post, payment_method)
        else
          add_credit_card(post, payment_method)
        end
      end

      def add_credit_card(post, payment_method)
        post[:ccname] = payment_method.name
        post[:ccnum] = payment_method.number
        post[:cvv2] = payment_method.verification_value
        post[:expyear] = format(payment_method.year, :four_digits)
        post[:expmon] = format(payment_method.month, :two_digits)
      end

      ACCOUNT_TYPES = {
        'checking' => '1',
        'savings' => '2'
      }

      def add_check(post, payment_method)
        post[:action] = 'ns_quicksale_check'
        post[:ckacct] = payment_method.account_number
        post[:ckaba] = payment_method.routing_number
        post[:ckno] = payment_method.number
        post[:ckaccttype] = ACCOUNT_TYPES[payment_method.account_type] if ACCOUNT_TYPES[payment_method.account_type]
      end

      def split_authorization(authorization)
        historyid, orderid = authorization.split('|')
        [historyid, orderid]
      end

      def add_reference(post, authorization)
        historyid, orderid = split_authorization(authorization)
        post[:postonly] = historyid
        post[:historykeyid] = historyid
        post[:orderkeyid] = orderid
      end

      def parse(xml)
        response = {}
        doc = Nokogiri::XML(xml)
        message = doc.xpath('//plaintext')
        message.text.split(/\r?\n/).each do |line|
          key, value = line.split(%r{=})
          response[key] = value if key
        end
        response
      end

      ACTIONS = {
        'purchase' => 'ns_quicksale_cc',
        'refund' => 'ns_credit',
        'authorize' => 'ns_quicksale_cc',
        'capture' => 'ns_quicksale_cc',
        'void' => 'ns_void'
      }

      def commit(action, options, post)
        post[:action] = ACTIONS[action] unless post[:action]
        post[:acctid] = @options[:acctid]
        post[:subid] = @options[:subid]
        post[:merchantpin] = @options[:merchantpin]

        post[:authonly] = '1' if action == 'authorize'

        raw = parse(ssl_post(url(options), post.to_query))

        succeeded = success_from(raw['result'])
        Response.new(
          succeeded,
          message_from(succeeded, raw),
          raw,
          authorization: authorization_from(raw),
          test: test?
        )
      end

      def success_from(result)
        result == '1'
      end

      def message_from(succeeded, response)
        if succeeded
          'Succeeded'
        else
          (response['transresult'] || response['Reason'] || 'Unable to read error message')
        end
      end

      def authorization_from(response)
        [response['historyid'], response['orderid']].join('|')
      end
    end
  end
end
