module ActiveMerchant # :nodoc:
  module Billing # :nodoc:
    class PacNetRavenGateway < Gateway
      AVS_ADDRESS_CODES = {
        'avs_address_unavailable'   => 'X',
        'avs_address_not_checked'   => 'X',
        'avs_address_matched'       => 'Y',
        'avs_address_not_matched'   => 'N',
        'avs_address_partial_match' => 'N'
      }

      AVS_POSTAL_CODES = {
        'avs_postal_unavailable'   => 'X',
        'avs_postal_not_checked'   => 'X',
        'avs_postal_matched'       => 'Y',
        'avs_postal_not_matched'   => 'N',
        'avs_postal_partial_match' => 'N'
      }

      CVV2_CODES = {
        'cvv2_matched'     => 'Y',
        'cvv2_not_matched' => 'N',
        'cvv2_unavailable' => 'X',
        'cvv2_not_checked' => 'X'
      }

      self.live_url = 'https://raven.deepcovelabs.net/realtime/'
      self.test_url = self.live_url

      self.supported_countries = ['US']
      self.supported_cardtypes = %i[visa master]
      self.money_format = :cents
      self.default_currency = 'USD'
      self.homepage_url = 'https://www.deepcovelabs.com/raven'
      self.display_name = 'Raven'

      def initialize(options = {})
        requires!(options, :user, :secret, :prn)
        super
      end

      def authorize(money, creditcard, options = {})
        post = {}
        add_creditcard(post, creditcard)
        add_currency_code(post, money, options)
        add_address(post, options)
        post['PRN'] = @options[:prn]

        commit('cc_preauth', money, post)
      end

      def purchase(money, creditcard, options = {})
        post = {}
        add_currency_code(post, money, options)
        add_creditcard(post, creditcard)
        add_address(post, options)
        post['PRN'] = @options[:prn]

        commit('cc_debit', money, post)
      end

      def void(authorization, options = {})
        post = {}
        post['TrackingNumber'] = authorization
        post['PymtType'] = options[:pymt_type]

        commit('void', nil, post)
      end

      def capture(money, authorization, options = {})
        post = {}
        post['PreauthNumber'] = authorization
        post['PRN'] = @options[:prn]
        add_currency_code(post, money, options)

        commit('cc_settle', money, post)
      end

      def refund(money, template_number, options = {})
        post = {}
        post['PRN'] = @options[:prn]
        post['TemplateNumber'] = template_number
        add_currency_code(post, money, options)

        commit('cc_refund', money, post)
      end

      private

      def add_creditcard(post, creditcard)
        post['CardNumber'] = creditcard.number
        post['Expiry'] = expdate(creditcard)
        post['CVV2'] = creditcard.verification_value if creditcard.verification_value
      end

      def add_currency_code(post, money, options)
        post['Currency'] = options[:currency] || currency(money)
      end

      def add_address(post, options)
        if address = options[:billing_address] || options[:address]
          post['BillingStreetAddressLineOne']   = address[:address1].to_s
          post['BillingStreetAddressLineFour']  = address[:address2].to_s
          post['BillingPostalCode']             = address[:zip].to_s
        end
      end

      def parse(body)
        Hash[body.split('&').map { |x| x.split('=').map { |y| CGI.unescape(y) } }]
      end

      def commit(action, money, parameters)
        parameters['Amount'] = amount(money) unless action == 'void'

        data = ssl_post url(action), post_data(action, parameters)

        response = parse(data)
        response[:action] = action

        message = message_from(response)

        test_mode = test? || message =~ /TESTMODE/

        Response.new(
          success?(response),
          message,
          response,
          test: test_mode,
          authorization: response['TrackingNumber'],
          fraud_review: fraud_review?(response),
          avs_result: {
            postal_match: AVS_POSTAL_CODES[response['AVSPostalResponseCode']],
            street_match: AVS_ADDRESS_CODES[response['AVSAddressResponseCode']]
          },
          cvv_result: CVV2_CODES[response['CVV2ResponseCode']]
        )
      end

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

      def endpoint(action)
        return 'void' if action == 'void'

        'submit'
      end

      def fraud_review?(response)
        false
      end

      def success?(response)
        if %w(cc_settle cc_debit cc_preauth cc_refund).include?(response[:action])
          !response['ApprovalCode'].nil? && response['ErrorCode'].nil? && (response['Status'] == 'Approved')
        elsif response[:action] = 'void'
          !response['ApprovalCode'].nil? && response['ErrorCode'].nil? && (response['Status'] == 'Voided')
        end
      end

      def message_from(response)
        return response['Message'] if response['Message']

        if response['Status'] == 'Approved'
          'This transaction has been approved'
        elsif response['Status'] == 'Declined'
          'This transaction has been declined'
        elsif response['Status'] == 'Voided'
          'This transaction has been voided'
        else
          response['Status']
        end
      end

      def post_data(action, parameters = {})
        post = {}

        post['PymtType']      = action
        post['RAPIVersion']   = '2'
        post['UserName']      = @options[:user]
        post['Timestamp']     = timestamp
        post['RequestID']     = request_id
        post['Signature']     = signature(action, post, parameters)

        post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
      end

      def timestamp
        Time.now.strftime('%Y-%m-%dT%H:%M:%S.Z')
      end

      def request_id
        SecureRandom.uuid
      end

      def signature(action, post, parameters = {})
        string =
          if %w(cc_settle cc_debit cc_preauth cc_refund).include?(action)
            post['UserName'] + post['Timestamp'] + post['RequestID'] + post['PymtType'] + parameters['Amount'].to_s + parameters['Currency']
          elsif action == 'void'
            post['UserName'] + post['Timestamp'] + post['RequestID'] + parameters['TrackingNumber']
          else
            post['UserName']
          end
        OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new(@options[:secret]), @options[:secret], string)
      end
    end
  end
end
