require 'nokogiri'

module ActiveMerchant #:nodoc:
  module Billing #:nodoc:
    class BamboraApacGateway < Gateway
      self.live_url = 'https://www.bambora.co.nz/interface/api'
      self.test_url = 'https://demo.bambora.co.nz/interface/api'

      self.supported_countries = %w[AU NZ]
      self.supported_cardtypes = %i[visa master american_express diners_club jcb]

      self.homepage_url = 'http://www.bambora.com/'
      self.display_name = 'Bambora Asia-Pacific'

      self.money_format = :cents

      STANDARD_ERROR_CODE_MAPPING = {
        '05' => STANDARD_ERROR_CODE[:card_declined],
        '06' => STANDARD_ERROR_CODE[:processing_error],
        '14' => STANDARD_ERROR_CODE[:invalid_number],
        '54' => STANDARD_ERROR_CODE[:expired_card]
      }

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

      def purchase(money, payment, options = {})
        commit('SubmitSinglePayment') do |xml|
          xml.Transaction do
            xml.CustRef options[:order_id]
            xml.Amount amount(money)
            xml.TrnType '1'
            add_payment(xml, payment)
            add_credentials(xml, options)
            xml.TrnSource options[:ip]
          end
        end
      end

      def authorize(money, payment, options = {})
        commit('SubmitSinglePayment') do |xml|
          xml.Transaction do
            xml.CustRef options[:order_id]
            xml.Amount amount(money)
            xml.TrnType '2'
            add_payment(xml, payment)
            add_credentials(xml, options)
            xml.TrnSource options[:ip]
          end
        end
      end

      def capture(money, authorization, options = {})
        commit('SubmitSingleCapture') do |xml|
          xml.Capture do
            xml.Receipt authorization
            xml.Amount amount(money)
            add_credentials(xml, options)
          end
        end
      end

      def refund(money, authorization, options = {})
        commit('SubmitSingleRefund') do |xml|
          xml.Refund do
            xml.Receipt authorization
            xml.Amount amount(money)
            add_credentials(xml, options)
          end
        end
      end

      def void(authorization, options = {})
        commit('SubmitSingleVoid') do |xml|
          xml.Void do
            xml.Receipt authorization
            xml.Amount amount(options[:amount])
            add_credentials(xml, options)
          end
        end
      end

      def store(payment, options = {})
        commit('TokeniseCreditCard') do |xml|
          xml.TokeniseCreditCard do
            xml.CardNumber payment.number
            xml.ExpM format(payment.month, :two_digits)
            xml.ExpY format(payment.year, :four_digits)
            xml.TokeniseAlgorithmID options[:tokenise_algorithm_id] || 2
            xml.UserName @options[:username]
            xml.Password @options[:password]
          end
        end
      end

      def supports_scrubbing?
        true
      end

      def scrub(transcript)
        transcript.
          gsub(%r((<CardNumber>)[^<]+(<))i, '\1[FILTERED]\2').
          gsub(%r((<CVN>)[^<]+(<))i, '\1[FILTERED]\2').
          gsub(%r((<Password>)[^<]+(<))i, '\1[FILTERED]\2')
      end

      private

      def add_credentials(xml, options)
        xml.AccountNumber options[:account_number] if options[:account_number]
        xml.Security do
          xml.UserName @options[:username]
          xml.Password @options[:password]
        end
      end

      def add_payment(xml, payment)
        if payment.is_a?(String)
          add_token(xml, payment)
        else
          add_credit_card(xml, payment)
        end
      end

      def add_token(xml, payment)
        xml.CreditCard do
          xml.TokeniseAlgorithmID options[:tokenise_algorithm_id] || 2
          xml.CardNumber payment
        end
      end

      def add_credit_card(xml, payment)
        xml.CreditCard Registered: 'False' do
          xml.CardNumber payment.number
          xml.ExpM format(payment.month, :two_digits)
          xml.ExpY format(payment.year, :four_digits)
          xml.CVN payment.verification_value
          xml.CardHolderName payment.name
        end
      end

      def parse(body)
        element = Nokogiri::XML(body).root.first_element_child.first_element_child

        response = {}
        doc = Nokogiri::XML(element)
        doc.root.elements.each do |e|
          response[e.name.underscore.to_sym] = e.inner_text
        end
        response
      end

      def commit(action, &block)
        headers = {
          'Content-Type' => 'text/xml; charset=utf-8',
          'SOAPAction' => "http://www.ippayments.com.au/interface/api/#{endpoint(action)}/#{action}"
        }
        response = parse(ssl_post("#{commit_url}/#{endpoint(action)}.asmx", new_submit_xml(action, &block), headers))

        Response.new(
          success_from(response),
          message_from(response),
          response,
          authorization: authorization_from(response),
          error_code: error_code_from(response),
          test: test?
        )
      end

      def new_submit_xml(action)
        xml = Builder::XmlMarkup.new(indent: 2)
        xml.instruct!
        xml.soap :Envelope, 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' do
          xml.soap :Body do
            xml.__send__(action, 'xmlns' => "http://www.ippayments.com.au/interface/api/#{endpoint(action)}") do
              if action == 'TokeniseCreditCard'
                xml.tokeniseCreditCardXML do
                  inner_xml = Builder::XmlMarkup.new(indent: 2)
                  yield(inner_xml)
                  xml.cdata!(inner_xml.target!)
                end
              else
                xml.trnXML do
                  inner_xml = Builder::XmlMarkup.new(indent: 2)
                  yield(inner_xml)
                  xml.cdata!(inner_xml.target!)
                end
              end
            end
          end
        end
        xml.target!
      end

      def endpoint(action)
        action == 'TokeniseCreditCard' ? 'sipp' : 'dts'
      end

      def commit_url
        test? ? test_url : live_url
      end

      def success_from(response)
        response[:response_code] == '0' || response[:return_value] == '0'
      end

      def error_code_from(response)
        STANDARD_ERROR_CODE_MAPPING[response[:declined_code]]
      end

      def message_from(response)
        success_from(response) ? 'Succeeded' : response[:declined_message]
      end

      def authorization_from(response)
        response[:receipt] || response[:token]
      end
    end
  end
end
