module ActiveMerchant #:nodoc:
  module Billing #:nodoc:
    class CamsGateway < Gateway
      self.live_url = 'https://secure.centralams.com/gw/api/transact.php'

      self.supported_countries = ['US']
      self.default_currency = 'USD'
      self.supported_cardtypes = %i[visa master american_express discover]

      self.homepage_url = 'https://www.centralams.com/'
      self.display_name = 'CAMS: Central Account Management System'

      STANDARD_ERROR_CODE_MAPPING = {
        '200' => STANDARD_ERROR_CODE[:card_declined],
        '201' => STANDARD_ERROR_CODE[:card_declined],
        '202' => STANDARD_ERROR_CODE[:card_declined],
        '203' => STANDARD_ERROR_CODE[:card_declined],
        '204' => STANDARD_ERROR_CODE[:card_declined],
        '220' => STANDARD_ERROR_CODE[:card_declined],
        '221' => STANDARD_ERROR_CODE[:card_declined],
        '222' => STANDARD_ERROR_CODE[:incorrect_number],
        '223' => STANDARD_ERROR_CODE[:expired_card],
        '224' => STANDARD_ERROR_CODE[:invalid_expiry_date],
        '225' => STANDARD_ERROR_CODE[:invalid_cvc],
        '240' => STANDARD_ERROR_CODE[:call_issuer],
        '250' => STANDARD_ERROR_CODE[:pickup_card],
        '251' => STANDARD_ERROR_CODE[:pickup_card],
        '252' => STANDARD_ERROR_CODE[:pickup_card],
        '253' => STANDARD_ERROR_CODE[:pickup_card],
        '260' => STANDARD_ERROR_CODE[:card_declined],
        '261' => STANDARD_ERROR_CODE[:card_declined],
        '262' => STANDARD_ERROR_CODE[:card_declined],
        '263' => STANDARD_ERROR_CODE[:processing_error],
        '264' => STANDARD_ERROR_CODE[:card_declined],
        '300' => STANDARD_ERROR_CODE[:card_declined],
        '400' => STANDARD_ERROR_CODE[:processing_error],
        '410' => STANDARD_ERROR_CODE[:processing_error],
        '411' => STANDARD_ERROR_CODE[:processing_error],
        '420' => STANDARD_ERROR_CODE[:processing_error],
        '421' => STANDARD_ERROR_CODE[:processing_error],
        '430' => STANDARD_ERROR_CODE[:processing_error],
        '440' => STANDARD_ERROR_CODE[:processing_error],
        '441' => STANDARD_ERROR_CODE[:processing_error],
        '460' => STANDARD_ERROR_CODE[:invalid_number],
        '461' => STANDARD_ERROR_CODE[:processing_error],
        '801' => STANDARD_ERROR_CODE[:processing_error],
        '811' => STANDARD_ERROR_CODE[:processing_error],
        '812' => STANDARD_ERROR_CODE[:processing_error],
        '813' => STANDARD_ERROR_CODE[:processing_error],
        '814' => STANDARD_ERROR_CODE[:processing_error],
        '815' => STANDARD_ERROR_CODE[:processing_error],
        '823' => STANDARD_ERROR_CODE[:processing_error],
        '824' => STANDARD_ERROR_CODE[:processing_error],
        '881' => STANDARD_ERROR_CODE[:processing_error],
        '882' => STANDARD_ERROR_CODE[:processing_error],
        '883' => STANDARD_ERROR_CODE[:processing_error],
        '884' => STANDARD_ERROR_CODE[:card_declined],
        '885' => STANDARD_ERROR_CODE[:card_declined],
        '886' => STANDARD_ERROR_CODE[:card_declined],
        '887' => STANDARD_ERROR_CODE[:processing_error],
        '888' => STANDARD_ERROR_CODE[:processing_error],
        '889' => STANDARD_ERROR_CODE[:processing_error],
        '890' => STANDARD_ERROR_CODE[:processing_error],
        '891' => STANDARD_ERROR_CODE[:incorrect_cvc],
        '892' => STANDARD_ERROR_CODE[:incorrect_cvc],
        '893' => STANDARD_ERROR_CODE[:processing_error],
        '894' => STANDARD_ERROR_CODE[:processing_error]
      }

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

      def purchase(money, payment, options = {})
        post = {}
        add_invoice(post, money, options)

        if payment.respond_to?(:number)
          add_payment(post, payment)
          add_address(post, payment, options)
        elsif payment.kind_of?(String)
          post[:transactionid] = split_authorization(payment)[0]
        end

        commit('sale', post)
      end

      def authorize(money, payment, options = {})
        post = {}
        add_invoice(post, money, options)
        add_payment(post, payment)
        add_address(post, payment, options)

        commit('auth', post)
      end

      def capture(money, authorization, options = {})
        post = {}
        add_reference(post, authorization)
        add_invoice(post, money, options)

        commit('capture', post)
      end

      def refund(money, authorization, options = {})
        post = {}
        add_reference(post, authorization)
        add_invoice(post, money, options)
        commit('refund', post)
      end

      def void(authorization, options = {})
        post = {}
        add_reference(post, authorization)
        commit('void', post)
      end

      def verify(credit_card, options = {})
        post = {}
        add_invoice(post, 0, options)
        add_payment(post, credit_card)
        add_address(post, credit_card, options)
        commit('verify', post)
      end

      def supports_scrubbing?
        true
      end

      def scrub(transcript)
        %w(ccnumber cvv password).each do |field|
          transcript = transcript.gsub(%r((#{field}=)[^&]+), '\1[FILTERED]\2')
        end

        transcript
      end

      private

      def add_address(post, creditcard, options = {})
        post[:firstname] = creditcard.first_name
        post[:lastname]  = creditcard.last_name

        return unless options[:billing_address]

        address = options[:billing_address]
        post[:address1] = address[:address1]
        post[:address2] = address[:address2]
        post[:city]     = address[:city]
        post[:state]    = address[:state]
        post[:zip]      = address[:zip]
        post[:country]  = address[:country]
        post[:phone]    = address[:phone]
      end

      def add_reference(post, authorization)
        transaction_id, authcode = split_authorization(authorization)
        post['transactionid'] = transaction_id
        post['authcode']      = authcode
      end

      def add_invoice(post, money, options)
        post[:amount] = amount(money)
        post[:currency] = (options[:currency] || default_currency)
      end

      def add_payment(post, payment)
        post[:ccnumber] = payment.number
        post[:ccexp] = "#{payment.month.to_s.rjust(2, '0')}#{payment.year.to_s[-2..-1]}"
        post[:cvv] = payment.verification_value
      end

      def parse(body)
        kvs = body.split('&')

        kvs.inject({}) { |h, kv|
          k, v = kv.split('=')
          h[k] = v
          h
        }
      end

      def commit(action, parameters)
        url = live_url
        parameters[:type] = action

        response_body = ssl_post(url, post_data(parameters))
        response = parse(response_body)

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

      def success_from(response)
        response['response_code'] == '100'
      end

      def message_from(response)
        response['responsetext']
      end

      def authorization_from(response)
        [response['transactionid'], response['authcode']].join('#')
      end

      def split_authorization(authorization)
        transaction_id, authcode = authorization.split('#')
        [transaction_id, authcode]
      end

      def post_data(parameters = {})
        parameters[:password] = @options[:password]
        parameters[:username] = @options[:username]

        parameters.collect { |k, v| "#{k}=#{v}" }.join('&')
      end

      def error_code_from(response)
        STANDARD_ERROR_CODE_MAPPING[response['response_code']]
      end
    end
  end
end
