module ActiveMerchant # :nodoc:
  module Billing # :nodoc:
    class AxcessmsGateway < Gateway
      self.test_url = 'https://test.ctpe.io/payment/ctpe'
      self.live_url = 'https://ctpe.io/payment/ctpe'

      self.supported_countries = %w(AD AT BE BG BR CA CH CY CZ DE DK EE ES FI FO FR GB
                                    GI GR HR HU IE IL IM IS IT LI LT LU LV MC MT MX NL
                                    NO PL PT RO RU SE SI SK TR US VA)

      self.supported_cardtypes = %i[visa master american_express discover jcb maestro]

      self.homepage_url = 'http://www.axcessms.com/'
      self.display_name = 'Axcess MS'
      self.money_format = :dollars
      self.default_currency = 'GBP'

      API_VERSION = '1.0'
      PAYMENT_CODE_PREAUTHORIZATION = 'CC.PA'
      PAYMENT_CODE_DEBIT = 'CC.DB'
      PAYMENT_CODE_CAPTURE = 'CC.CP'
      PAYMENT_CODE_REVERSAL = 'CC.RV'
      PAYMENT_CODE_REFUND = 'CC.RF'
      PAYMENT_CODE_REBILL = 'CC.RB'

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

      def purchase(money, payment, options = {})
        payment_code = payment.respond_to?(:number) ? PAYMENT_CODE_DEBIT : PAYMENT_CODE_REBILL
        commit(payment_code, money, payment, options)
      end

      def authorize(money, authorization, options = {})
        commit(PAYMENT_CODE_PREAUTHORIZATION, money, authorization, options)
      end

      def capture(money, authorization, options = {})
        commit(PAYMENT_CODE_CAPTURE, money, authorization, options)
      end

      def refund(money, authorization, options = {})
        commit(PAYMENT_CODE_REFUND, money, authorization, options)
      end

      def void(authorization, options = {})
        commit(PAYMENT_CODE_REVERSAL, nil, authorization, options)
      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

      private

      def commit(paymentcode, money, payment, options)
        options[:mode] ||= (test? ? 'INTEGRATOR_TEST' : 'LIVE')
        request = build_request(paymentcode, money, payment, options)

        headers = {
          'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8'
        }

        response = parse(ssl_post((test? ? test_url : live_url), "load=#{CGI.escape(request)}", headers))
        success = (response[:result] == 'ACK')
        message = "#{response[:reason]} - #{response[:return]}"
        authorization = response[:unique_id]

        Response.new(
          success,
          message,
          response,
          authorization:,
          test: (response[:mode] != 'LIVE')
        )
      end

      def parse(body)
        return {} if body.blank?

        xml = REXML::Document.new(body)

        response = {}
        xml.root.elements.to_a.each do |node|
          parse_element(response, node)
        end

        response[:mode] = REXML::XPath.first(xml, '//Transaction').attributes['mode']

        response
      end

      def parse_element(response, node)
        node.attributes.each { |name, value| response["#{node.name}_#{name}".underscore.to_sym] = value } if node.has_attributes?

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

      def build_request(payment_code, money, payment, options)
        xml = Builder::XmlMarkup.new indent: 2
        xml.instruct!
        xml.tag! 'Request', 'version' => API_VERSION do
          xml.tag! 'Header' do
            xml.tag! 'Security', 'sender' => @options[:sender]
          end
          xml.tag! 'Transaction', 'mode' => options[:mode], 'channel' => @options[:channel], 'response' => 'SYNC' do
            xml.tag! 'User', 'login' => @options[:login], 'pwd' => @options[:password]
            xml.tag! 'Identification' do
              xml.tag! 'TransactionID', options[:order_id] || generate_unique_id
              xml.tag! 'ReferenceID', payment unless payment.respond_to?(:number)
            end

            xml.tag! 'Payment', 'code' => payment_code do
              xml.tag! 'Memo', options[:memo] unless options[:memo].blank?
              xml.tag! 'Presentation' do
                xml.tag! 'Amount', amount(money)
                xml.tag! 'Currency', (options[:currency] || currency(money))
                xml.tag! 'Usage', options[:description]
              end
            end

            if payment.respond_to?(:number)
              add_payment(xml, payment)

              xml.tag! 'Customer' do
                add_customer_name(xml, payment)
                add_address(xml, options[:billing_address] || options[:address])
                add_contact(xml, options)
              end
            end
          end
        end

        xml.target!
      end

      def add_contact(xml, options)
        xml.tag! 'Contact' do
          xml.tag! 'Email', (options[:email] || 'unknown@example.com')
          xml.tag! 'Ip', (options[:ip] || '127.0.0.1')
        end
      end

      def add_customer_name(xml, payment)
        xml.tag! 'Name' do
          xml.tag! 'Given', payment.first_name
          xml.tag! 'Family', payment.last_name
        end
      end

      def add_payment(xml, payment)
        xml.tag! 'Account' do
          xml.tag! 'Number', payment.number
          xml.tag! 'Holder', payment.name
          xml.tag! 'Brand', payment.brand
          xml.tag! 'Expiry', 'month' => payment.month, 'year' => payment.year
          xml.tag! 'Verification', payment.verification_value
        end
      end

      def add_address(xml, address)
        raise ArgumentError.new('Address is required') unless address

        xml.tag! 'Address' do
          xml.tag! 'Street', "#{address[:address1]} #{address[:address2]}".strip
          xml.tag! 'City', address[:city]
          xml.tag! 'State', address[:state]
          xml.tag! 'Zip', address[:zip]
          xml.tag! 'Country', address[:country]
        end
      end
    end
  end
end
