module ActiveMerchant # :nodoc:
  module Billing # :nodoc:
    # CC5 API is used by many banks in Turkey. Extend this base class to provide
    # concrete implementations.
    class CC5Gateway < Gateway
      self.default_currency = 'TRY'

      CURRENCY_CODES = {
        'TRY' => 949,
        'YTL' => 949,
        'TRL' => 949,
        'TL'  => 949,
        'USD' => 840,
        'EUR' => 978,
        'GBP' => 826,
        'JPY' => 392
      }

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

      def purchase(money, creditcard, options = {})
        commit(build_sale_request('Auth', money, creditcard, options))
      end

      def authorize(money, creditcard, options = {})
        commit(build_sale_request('PreAuth', money, creditcard, options))
      end

      def capture(money, authorization, options = {})
        commit(build_capture_request(money, authorization, options))
      end

      def void(authorization, options = {})
        commit(build_void_request(authorization, options))
      end

      def refund(money, authorization, options = {})
        commit(build_authorization_credit_request(money, authorization, options))
      end

      def credit(money, creditcard, options = {})
        commit(build_creditcard_credit_request(money, creditcard, options))
      end

      protected

      def build_sale_request(type, money, creditcard, options = {})
        requires!(options, :order_id)

        xml = Builder::XmlMarkup.new indent: 2

        xml.tag! 'CC5Request' do
          add_login_tags(xml)
          xml.tag! 'OrderId', options[:order_id]
          xml.tag! 'Type', type
          xml.tag! 'Number', creditcard.number
          xml.tag! 'Expires', [format(creditcard.month, :two_digits), format(creditcard.year, :two_digits)].join('/')
          xml.tag! 'Cvv2Val', creditcard.verification_value
          add_amount_tags(money, options, xml)
          xml.tag! 'Email', options[:email] if options[:email]

          if (address = (options[:billing_address] || options[:address]))
            xml.tag! 'BillTo' do
              add_address(xml, address)
            end
            xml.tag! 'ShipTo' do
              add_address(xml, address)
            end
          end
        end

        xml.target!
      end

      def build_capture_request(money, authorization, options = {})
        xml = Builder::XmlMarkup.new indent: 2

        xml.tag! 'CC5Request' do
          add_login_tags(xml)
          xml.tag! 'OrderId', authorization
          xml.tag! 'Type', 'PostAuth'
          add_amount_tags(money, options, xml)
        end
      end

      def build_void_request(authorization, options = {})
        xml = Builder::XmlMarkup.new indent: 2

        xml.tag! 'CC5Request' do
          add_login_tags(xml)
          xml.tag! 'OrderId', authorization
          xml.tag! 'Type', 'Void'
        end
      end

      def build_authorization_credit_request(money, authorization, options = {})
        xml = Builder::XmlMarkup.new indent: 2

        xml.tag! 'CC5Request' do
          add_login_tags(xml)
          xml.tag! 'OrderId', authorization
          xml.tag! 'Type', 'Credit'
          add_amount_tags(money, options, xml)
        end
      end

      def build_creditcard_credit_request(money, creditcard, options = {})
        xml = Builder::XmlMarkup.new indent: 2

        xml.tag! 'CC5Request' do
          add_login_tags(xml)
          xml.tag! 'Type', 'Credit'
          xml.tag! 'Number', creditcard.number

          add_amount_tags(money, options, xml)
        end
      end

      def add_address(xml, address)
        xml.tag! 'Name', normalize(address[:name])
        xml.tag! 'Street1', normalize(address[:address1])
        xml.tag! 'Street2', normalize(address[:address2]) if address[:address2]
        xml.tag! 'City', normalize(address[:city])
        xml.tag! 'PostalCode', address[:zip]
        xml.tag! 'Country', normalize(address[:country])
        xml.tag! 'Company', normalize(address[:company])
        xml.tag! 'TelVoice', address[:phone].to_s.gsub(/[^0-9]/, '') if address[:phone]
      end

      def add_login_tags(xml)
        xml.tag! 'Name', @options[:login]
        xml.tag! 'Password', @options[:password]
        xml.tag! 'ClientId', @options[:client_id]
        xml.tag! 'Mode', (test? ? 'T' : 'P')
      end

      def add_amount_tags(money, options, xml)
        xml.tag! 'Total', amount(money)
        xml.tag! 'Currency', currency_code(options[:currency] || currency(money))
      end

      def currency_code(currency)
        (CURRENCY_CODES[currency] || CURRENCY_CODES[default_currency])
      end

      def commit(request)
        raw_response = ssl_post((test? ? self.test_url : self.live_url), 'DATA=' + request)

        response = parse(raw_response)

        success = success?(response)

        Response.new(
          success,
          (success ? 'Approved' : "Declined (Reason: #{response[:proc_return_code]} - #{response[:err_msg]})"),
          response,
          test: test?,
          authorization: response[:order_id]
        )
      end

      def parse(body)
        xml = REXML::Document.new(body)

        response = {}
        xml.root.elements.to_a.each do |node|
          parse_element(response, node)
        end
        response
      end

      def parse_element(response, node)
        if node.has_elements?
          node.elements.each { |element| parse_element(response, element) }
        else
          response[node.name.underscore.to_sym] = node.text
        end
      end

      def success?(response)
        (response[:response] == 'Approved')
      end

      def normalize(text)
        return unless text

        if ActiveSupport::Inflector.method(:transliterate).arity == -2
          ActiveSupport::Inflector.transliterate(text, '')
        else
          text.gsub(/[^\x00-\x7F]+/, '')
        end
      end
    end
  end
end
