module ActiveMerchant #:nodoc:
  module Billing #:nodoc:
    class ModernPaymentsCimGateway < Gateway #:nodoc:
      self.test_url = 'https://secure.modpay.com/netservices/test/ModpayTest.asmx'
      self.live_url = 'https://secure.modpay.com/ws/modpay.asmx'

      LIVE_XMLNS = 'https://secure.modpay.com/ws/'
      TEST_XMLNS = 'https://secure.modpay.com/netservices/test/'

      self.supported_countries = ['US']
      self.supported_cardtypes = %i[visa master american_express discover]
      self.homepage_url = 'http://www.modpay.com'
      self.display_name = 'Modern Payments'

      SUCCESS_MESSAGE = 'Transaction accepted'
      FAILURE_MESSAGE = 'Transaction failed'
      ERROR_MESSAGE   = 'Transaction error'

      PAYMENT_METHOD = {
        check: 1,
        credit_card: 2
      }

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

      def create_customer(options = {})
        post = {}
        add_customer_data(post, options)
        add_address(post, options)

        commit('CreateCustomer', post)
      end

      def modify_customer_credit_card(customer_id, credit_card)
        raise ArgumentError, 'The customer_id cannot be blank' if customer_id.blank?

        post = {}
        add_customer_id(post, customer_id)
        add_credit_card(post, credit_card)

        commit('ModifyCustomerCreditCard', post)
      end

      def authorize_credit_card_payment(customer_id, amount)
        raise ArgumentError, 'The customer_id cannot be blank' if customer_id.blank?

        post = {}
        add_customer_id(post, customer_id)
        add_amount(post, amount)

        commit('AuthorizeCreditCardPayment', post)
      end

      def create_payment(customer_id, amount, options = {})
        raise ArgumentError, 'The customer_id cannot be blank' if customer_id.blank?

        post = {}
        add_customer_id(post, customer_id)
        add_amount(post, amount)
        add_payment_details(post, options)

        commit('CreatePayment', post)
      end

      private

      def add_payment_details(post, options)
        post[:pmtDate] = (options[:payment_date] || Time.now.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
        post[:pmtType] = PAYMENT_METHOD[options[:payment_method] || :credit_card]
      end

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

      def add_customer_id(post, customer_id)
        post[:custId] = customer_id
      end

      def add_customer_data(post, options)
        post[:acctNum] = options[:customer]
      end

      def add_address(post, options)
        address = options[:billing_address] || options[:address] || {}

        if name = address[:name]
          post[:firstName], post[:lastName] = split_names(name)
        else
          post[:firstName] = address[:first_name]
          post[:lastName]  = address[:last_name]
        end

        post[:address]   = address[:address1]
        post[:city]      = address[:city]
        post[:state]     = address[:state]
        post[:zip]       = address[:zip]
        post[:phone]     = address[:phone]
        post[:fax]       = address[:fax]
        post[:email]     = options[:email]
      end

      def add_credit_card(post, credit_card)
        post[:ccName] = credit_card.name
        post[:ccNum]  = credit_card.number
        post[:expMonth] = credit_card.month
        post[:expYear]  = credit_card.year
      end

      def build_request(action, params)
        envelope_obj = { 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
          'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/',
          'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance' }
        xml = Builder::XmlMarkup.new indent: 2
        xml.instruct!
        xml.tag! 'env:Envelope', envelope_obj do
          xml.tag! 'env:Body' do
            xml.tag! action, { 'xmlns' => xmlns(action) } do
              xml.tag! 'clientId', @options[:login]
              xml.tag! 'clientCode', @options[:password]
              params.each { |key, value| xml.tag! key, value }
            end
          end
        end
        xml.target!
      end

      def xmlns(action)
        if test? && action == 'AuthorizeCreditCardPayment'
          TEST_XMLNS
        else
          LIVE_XMLNS
        end
      end

      def url(action)
        if test? && action == 'AuthorizeCreditCardPayment'
          self.test_url
        else
          self.live_url
        end
      end

      def commit(action, params)
        data = ssl_post(
          url(action),
          build_request(action, params),
          {
            'Content-Type' => 'text/xml; charset=utf-8',
            'SOAPAction' => "#{xmlns(action)}#{action}"
          }
        )

        response = parse(action, data)
        Response.new(
          successful?(action, response),
          message_from(action, response),
          response,
          test: test?,
          authorization: authorization_from(action, response),
          avs_result: { code: response[:avs_code] }
        )
      end

      def authorization_from(action, response)
        response[authorization_key(action)]
      end

      def authorization_key(action)
        action == 'AuthorizeCreditCardPayment' ? :trans_id : "#{action.underscore}_result".to_sym
      end

      def successful?(action, response)
        key = authorization_key(action)

        if key == :trans_id
          response[:approved] == 'true'
        else
          response[key].to_i > 0
        end
      end

      def message_from(action, response)
        if response[:faultcode]
          ERROR_MESSAGE
        elsif successful?(action, response)
          SUCCESS_MESSAGE
        else
          FAILURE_MESSAGE
        end
      end

      def parse(action, xml)
        response = {}
        response[:action] = action

        xml = REXML::Document.new(xml)
        if root = REXML::XPath.first(xml, "//#{action}Response")
          root.elements.to_a.each do |node|
            parse_element(response, node)
          end
        elsif root = REXML::XPath.first(xml, '//soap:Fault')
          root.elements.to_a.each do |node|
            response[node.name.underscore.to_sym] = node.text
          end
        end

        response
      end

      def parse_element(response, node)
        if node.has_elements?
          node.elements.each { |e| parse_element(response, e) }
        else
          response[node.name.underscore.to_sym] = node.text.to_s.strip
        end
      end
    end
  end
end
