require 'rexml/document'

module ActiveMerchant #:nodoc:
  module Billing #:nodoc:
    # This class implements the Psigate gateway for the ActiveMerchant module.
    #
    # Modifications by Sean O'Hara ( sohara at sohara dot com )
    #
    # Usage for a PreAuth (authorize) is as follows:
    #
    # gateway = PsigateGateway.new(
    #   :login => 'teststore',
    #   :password => 'psigate1234'
    # )
    #
    # creditcard = CreditCard.new(
    #   :number => '4242424242424242',
    #   :month => 8,
    #   :year => 2006,
    #   :first_name => 'Longbob',
    #   :last_name => 'Longsen'
    # )
    #
    # twenty = 2000
    # response = @gateway.authorize(twenty, creditcard,
    #    :order_id =>  1234,
    #    :billing_address => {
    #      :address1 => '123 fairweather Lane',
    #      :address2 => 'Apt B',
    #      :city => 'New York',
    #      :state => 'NY',
    #      :country => 'U.S.A.',
    #      :zip => '10010'
    #   },
    #   :email => 'jack@yahoo.com'
    # )
    class PsigateGateway < Gateway
      self.test_url  = 'https://dev.psigate.com:7989/Messenger/XMLMessenger'
      self.live_url  = 'https://secure.psigate.com:7934/Messenger/XMLMessenger'

      self.supported_cardtypes = [:visa, :master, :american_express]
      self.supported_countries = ['CA']
      self.homepage_url = 'http://www.psigate.com/'
      self.display_name = 'Psigate'

      SUCCESS_MESSAGE = 'Success'
      FAILURE_MESSAGE = 'The transaction was declined'

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

      def authorize(money, creditcard, options = {})
        requires!(options, :order_id)
        options[:CardAction] = "1"
        commit(money, creditcard, options)
      end

      def purchase(money, creditcard, options = {})
        requires!(options, :order_id)
        options[:CardAction] = "0"
        commit(money, creditcard, options)
      end

      def capture(money, authorization, options = {})
        options[:CardAction] = "2"
        options[:order_id], options[:trans_ref_number] = split_authorization(authorization)
        commit(money, nil, options)
      end

      def credit(money, authorization, options = {})
        deprecated CREDIT_DEPRECATION_MESSAGE
        refund(money, authorization, options)
      end

      def refund(money, authorization, options = {})
        options[:CardAction] = "3"
        options[:order_id], options[:trans_ref_number] = split_authorization(authorization)
        commit(money, nil, options)
      end

      def void(authorization, options = {})
        options[:CardAction] = "9"
        options[:order_id], options[:trans_ref_number] = split_authorization(authorization)
        commit(nil, nil, options)
      end

      private

      def commit(money, creditcard, options = {})
        response = parse(ssl_post(url, post_data(money, creditcard, options)))

        Response.new(successful?(response), message_from(response), response,
          :test => test?,
          :authorization => build_authorization(response) ,
          :avs_result => { :code => response[:avsresult] },
          :cvv_result => response[:cardidresult]
        )
      end

      def url
        (test? ? self.test_url : self.live_url)
      end

      def successful?(response)
        response[:approved] == "APPROVED"
      end

      def parse(xml)
        response = {:message => "Global Error Receipt", :complete => false}

        xml = REXML::Document.new(xml)
        xml.elements.each('//Result/*') do |node|
          response[node.name.downcase.to_sym] = normalize(node.text)
        end unless xml.root.nil?

        response
      end

      def post_data(money, creditcard, options)
        xml = REXML::Document.new
        xml << REXML::XMLDecl.new
        root = xml.add_element("Order")

        parameters(money, creditcard, options).each do |key, value|
          root.add_element(key.to_s).text = value if value
        end

        xml.to_s
      end

      def parameters(money, creditcard, options = {})
        params = {
          # General order parameters
          :StoreID => @options[:login],
          :Passphrase => @options[:password],
          :TestResult => options[:test_result],
          :OrderID => options[:order_id],
          :UserID => options[:user_id],
          :Phone => options[:phone],
          :Fax => options[:fax],
          :Email => options[:email],
          :TransRefNumber => options[:trans_ref_number],

          # Credit Card parameters
          :PaymentType => "CC",
          :CardAction => options[:CardAction],

          # Financial parameters
          :CustomerIP => options[:ip],
          :SubTotal => amount(money),
          :Tax1 => options[:tax1],
          :Tax2 => options[:tax2],
          :ShippingTotal => options[:shipping_total],
        }

        if creditcard
          exp_month = sprintf("%.2i", creditcard.month) unless creditcard.month.blank?
          exp_year = creditcard.year.to_s[2,2] unless creditcard.year.blank?
          card_id_code = (creditcard.verification_value.blank? ? nil : "1")

          params.update(
            :CardNumber => creditcard.number,
            :CardExpMonth => exp_month,
            :CardExpYear => exp_year,
            :CardIDCode => card_id_code,
            :CardIDNumber => creditcard.verification_value
          )
        end

        if(address = (options[:billing_address] || options[:address]))
          params[:Bname] = address[:name] || creditcard.name
          params[:Baddress1]    = address[:address1] unless address[:address1].blank?
          params[:Baddress2]    = address[:address2] unless address[:address2].blank?
          params[:Bcity]        = address[:city]     unless address[:city].blank?
          params[:Bprovince]    = address[:state]    unless address[:state].blank?
          params[:Bpostalcode]  = address[:zip]      unless address[:zip].blank?
          params[:Bcountry]     = address[:country]  unless address[:country].blank?
          params[:Bcompany]     = address[:company]  unless address[:company].blank?
        end

        if address = options[:shipping_address]
          params[:Sname]        = address[:name] || creditcard.name
          params[:Saddress1]    = address[:address1] unless address[:address1].blank?
          params[:Saddress2]    = address[:address2] unless address[:address2].blank?
          params[:Scity]        = address[:city]     unless address[:city].blank?
          params[:Sprovince]    = address[:state]    unless address[:state].blank?
          params[:Spostalcode]  = address[:zip]      unless address[:zip].blank?
          params[:Scountry]     = address[:country]  unless address[:country].blank?
          params[:Scompany]     = address[:company]  unless address[:company].blank?
        end

        params
      end

      def message_from(response)
        if response[:approved] == "APPROVED"
          return SUCCESS_MESSAGE
        else
          return FAILURE_MESSAGE if response[:errmsg].blank?
          return response[:errmsg].gsub(/[^\w]/, ' ').split.join(" ").capitalize
        end
      end

      # Make a ruby type out of the response string
      def normalize(field)
        case field
        when "true"   then true
        when "false"  then false
        when ""       then nil
        when "null"   then nil
        else field
        end
      end

      def split_authorization(authorization)
        order_id, trans_ref_number = authorization.split(';')
        [order_id, trans_ref_number]
      end

      def build_authorization(response)
        [response[:orderid], response[:transrefnumber]].join(";")
      end
    end
  end
end