require 'rexml/document'

module ActiveMerchant # :nodoc:
  module Billing # :nodoc:
    class VerifiGateway < Gateway
      class VerifiPostData < PostData
        # Fields that will be sent even if they are blank
        self.required_fields = %i[amount type ccnumber ccexp firstname lastname
                                  company address1 address2 city state zip country phone]
      end

      self.live_url = self.test_url = 'https://secure.verifi.com/gw/api/transact.php'

      RESPONSE_CODE_MESSAGES = {
        '100' => 'Transaction was Approved',
        '200' => 'Transaction was Declined by Processor',
        '201' => 'Do Not Honor',
        '202' => 'Insufficient Funds',
        '203' => 'Over Limit',
        '204' => 'Transaction not allowed',
        '220' => 'Incorrect payment Data',
        '221' => 'No Such Card Issuer',
        '222' => 'No Card Number on file with Issuer',
        '223' => 'Expired Card',
        '224' => 'Invalid Expiration Date',
        '225' => 'Invalid Card Security Code',
        '240' => 'Call Issuer for Further Information',
        '250' => 'Pick Up Card',
        '251' => 'Lost Card',
        '252' => 'Stolen Card',
        '253' => 'Fraudulent Card',
        '260' => 'Declined With further Instructions Available (see response text)',
        '261' => 'Declined - Stop All Recurring Payments',
        '262' => 'Declined - Stop this Recurring Program',
        '263' => 'Declined - Update Cardholder Data Available',
        '264' => 'Declined - Retry in a few days',
        '300' => 'Transaction was Rejected by Gateway',
        '400' => 'Transaction Error Returned by Processor',
        '410' => 'Invalid Merchant Configuration',
        '411' => 'Merchant Account is Inactive',
        '420' => 'Communication Error',
        '421' => 'Communication Error with Issuer',
        '430' => 'Duplicate Transaction at Processor',
        '440' => 'Processor Format Error',
        '441' => 'Invalid Transaction Information',
        '460' => 'Processor Feature Not Available',
        '461' => 'Unsupported Card Type'
      }

      SUCCESS = 1

      TRANSACTIONS = {
        authorization: 'auth',
        purchase: 'sale',
        capture: 'capture',
        void: 'void',
        credit: 'credit',
        refund: 'refund'
      }

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

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

      def purchase(money, credit_card, options = {})
        sale_authorization_or_credit_template(:purchase, money, credit_card, options)
      end

      def authorize(money, credit_card, options = {})
        sale_authorization_or_credit_template(:authorization, money, credit_card, options)
      end

      def capture(money, authorization, options = {})
        capture_void_or_refund_template(:capture, money, authorization, options)
      end

      def void(authorization, options = {})
        capture_void_or_refund_template(:void, 0, authorization, options)
      end

      def credit(money, credit_card_or_authorization, options = {})
        if credit_card_or_authorization.is_a?(String)
          ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
          refund(money, credit_card_or_authorization, options)
        else
          sale_authorization_or_credit_template(:credit, money, credit_card_or_authorization, options)
        end
      end

      def refund(money, reference, options = {})
        capture_void_or_refund_template(:refund, money, reference, options)
      end

      private

      def sale_authorization_or_credit_template(trx_type, money, credit_card, options = {})
        post = VerifiPostData.new
        add_security_key_data(post, options, money)
        add_credit_card(post, credit_card)
        add_addresses(post, options)
        add_customer_data(post, options)
        add_invoice_data(post, options)
        add_optional_data(post, options)
        commit(trx_type, money, post)
      end

      def capture_void_or_refund_template(trx_type, money, authorization, options)
        post = VerifiPostData.new
        post[:transactionid] = authorization

        commit(trx_type, money, post)
      end

      def add_credit_card(post, credit_card)
        post[:ccnumber]  = credit_card.number
        post[:ccexp]     = expdate(credit_card)
        post[:firstname] = credit_card.first_name
        post[:lastname]  = credit_card.last_name
        post[:cvv]       = credit_card.verification_value
      end

      def add_addresses(post, options)
        if billing_address = options[:billing_address] || options[:address]
          post[:company]    = billing_address[:company]
          post[:address1]   = billing_address[:address1]
          post[:address2]   = billing_address[:address2]
          post[:city]       = billing_address[:city]
          post[:state]      = billing_address[:state]
          post[:zip]        = billing_address[:zip]
          post[:country]    = billing_address[:country]
          post[:phone]      = billing_address[:phone]
          post[:fax]        = billing_address[:fax]
        end

        if shipping_address = options[:shipping_address]
          post[:shipping_firstname] = shipping_address[:first_name]
          post[:shipping_lastname]  = shipping_address[:last_name]
          post[:shipping_company]   = shipping_address[:company]
          post[:shipping_address1]  = shipping_address[:address1]
          post[:shipping_address2]  = shipping_address[:address2]
          post[:shipping_city]      = shipping_address[:city]
          post[:shipping_state]     = shipping_address[:state]
          post[:shipping_zip]       = shipping_address[:zip]
          post[:shipping_country]   = shipping_address[:country]
          post[:shipping_email]     = shipping_address[:email]
        end
      end

      def add_customer_data(post, options)
        post[:email]     = options[:email]
        post[:ipaddress] = options[:ip]
      end

      def add_invoice_data(post, options)
        post[:orderid]            = options[:order_id]
        post[:ponumber]           = options[:invoice]
        post[:orderdescription]   = options[:description]
        post[:tax]                = options[:tax]
        post[:shipping]           = options[:shipping]
      end

      def add_optional_data(post, options)
        post[:billing_method]     = options[:billing_method]
        post[:website]            = options[:website]
        post[:descriptor]         = options[:descriptor]
        post[:descriptor_phone]   = options[:descriptor_phone]
        post[:cardholder_auth]    = options[:cardholder_auth]
        post[:cavv]               = options[:cavv]
        post[:xid]                = options[:xid]
        post[:customer_receipt]   = options[:customer_receipt]
      end

      def add_security_key_data(post, options, money)
        # MD5(username|password|orderid|amount|time)
        now = Time.now.to_i.to_s
        md5 = Digest::MD5.new
        md5 << (@options[:login].to_s + '|')
        md5 << (@options[:password].to_s + '|')
        md5 << (options[:order_id].to_s + '|')
        md5 << (amount(money).to_s + '|')
        md5 << now
        post[:key]  = md5.hexdigest
        post[:time] = now
      end

      def commit(trx_type, money, post)
        post[:amount] = amount(money)

        response = parse(ssl_post(self.live_url, post_data(trx_type, post)))

        Response.new(
          response[:response].to_i == SUCCESS,
          message_from(response),
          response,
          test: test?,
          authorization: response[:transactionid],
          avs_result: { code: response[:avsresponse] },
          cvv_result: response[:cvvresponse]
        )
      end

      def message_from(response)
        response[:response_code_message] || ''
      end

      def parse(body)
        results = {}
        CGI.parse(body).each { |key, value| results[key.intern] = value[0] }
        results[:response_code_message] = RESPONSE_CODE_MESSAGES[results[:response_code]] if results[:response_code]
        results
      end

      def post_data(trx_type, post)
        post[:username]   = @options[:login]
        post[:password]   = @options[:password]
        post[:type]       = TRANSACTIONS[trx_type]

        post.to_s
      end
    end
  end
end
