require 'base64'
require 'nokogiri'

module Spreedly
  class Environment

    include SslRequester
    include Urls

    attr_reader :key, :currency_code, :base_url

    def initialize(environment_key, access_secret, options={})
      @key, @access_secret = environment_key, access_secret
      @base_url = options[:base_url] || "https://core.spreedly.com"
      @currency_code = options[:currency_code] || 'USD'
    end

    def transparent_redirect_form_action
      "#{base_url}/v1/payment_methods"
    end

    def find_payment_method(token)
      xml_doc = ssl_get(find_payment_method_url(token), headers)
      PaymentMethod.new_from(xml_doc)
    end

    def find_transaction(token)
      xml_doc = ssl_get(find_transaction_url(token), headers)
      Transaction.new_from(xml_doc)
    end

    def find_gateway(token)
      xml_doc = ssl_get(find_gateway_url(token), headers)
      Gateway.new(xml_doc)
    end

    def purchase_on_gateway(gateway_token, payment_method_token, amount, options = {})
      body = auth_purchase_body(amount, payment_method_token, options)
      api_post(purchase_url(gateway_token), body)
    end

    def authorize_on_gateway(gateway_token, payment_method_token, amount, options = {})
      body = auth_purchase_body(amount, payment_method_token, options)
      api_post(authorize_url(gateway_token), body)
    end

    def capture_transaction(authorization_token, options = {})
      api_post(capture_url(authorization_token), capture_body(options))
    end

    def void_transaction(token, options = {})
      api_post(void_transaction_url(token), void_body(options))
    end

    def refund_transaction(token, options = {})
      api_post(refund_transaction_url(token), refund_body(options))
    end

    def retain_payment_method(payment_method_token)
      xml_doc = ssl_put(retain_payment_method_url(payment_method_token), '', headers)
      Transaction.new_from(xml_doc)
    end

    def redact_payment_method(payment_method_token, options = {})
      body = redact_payment_method_body(options)
      xml_doc = ssl_put(redact_payment_method_url(payment_method_token), body, headers)
      Transaction.new_from(xml_doc)
    end

    def redact_gateway(gateway_token, options = {})
      xml_doc = ssl_put(redact_gateway_url(gateway_token), '', headers)
      Transaction.new_from(xml_doc)
    end

    def list_transactions(since_token = nil, payment_method_token = nil)
      xml_doc = ssl_get(list_transactions_url(since_token, payment_method_token), headers)
      Transaction.new_list_from(xml_doc)
    end

    def list_payment_methods(since_token = nil)
      xml_doc = ssl_get(list_payment_methods_url(since_token), headers)
      PaymentMethod.new_list_from(xml_doc)
    end

    def list_gateways(since_token = nil)
      xml_doc = ssl_get(list_gateways_url(since_token), headers)
      Gateway.new_list_from(xml_doc)
    end

    def gateway_options
      xml_doc = ssl_options(gateway_options_url)
      GatewayClass.new_list_from(xml_doc)
    end

    def add_gateway(gateway_type, credentials = {})
      body = add_gateway_body(gateway_type, credentials)
      xml_doc = ssl_post(add_gateway_url, body, headers)
      Gateway.new(xml_doc)
    end

    def add_credit_card(options)
      api_post(add_payment_method_url, add_credit_card_body(options))
    end

    def update_credit_card(credit_card_token, options)
      body = update_credit_card_body(options)
      xml_doc = ssl_put(update_payment_method_url(credit_card_token), body, headers)
      PaymentMethod.new_from(xml_doc)
    end

    private
    def headers
      {
        'Authorization' => ('Basic ' + Base64.strict_encode64("#{@key}:#{@access_secret}").chomp),
        'Content-Type' => 'text/xml'
      }
    end

    def auth_purchase_body(amount, payment_method_token, options)
      build_xml_request('transaction') do |doc|
        doc.amount amount
        doc.currency_code(options[:currency_code] || currency_code)
        doc.payment_method_token(payment_method_token)
        add_to_doc(doc, options, :retain_on_success)
        add_extra_options_for_basic_ops(doc, options)
      end
    end

    def capture_body(options)
      return '' if options.empty?

      build_xml_request('transaction') do |doc|
        add_to_doc(doc, options, :amount, :currency_code)
        add_extra_options_for_basic_ops(doc, options)
      end
    end

    def void_body(options)
      return '' if options.empty?

      build_xml_request('transaction') do |doc|
        add_extra_options_for_basic_ops(doc, options)
      end
    end

    def refund_body(options)
      return '' if options.empty?

      build_xml_request('transaction') do |doc|
        add_to_doc(doc, options, :amount, :currency_code)
        add_extra_options_for_basic_ops(doc, options)
      end
    end

    def redact_payment_method_body(options)
      return '' if options.empty?
      build_xml_request('transaction') do |doc|
        add_to_doc(doc, options, :remove_from_gateway)
      end
    end

    def add_gateway_body(gateway_type, credentials)
      build_xml_request('gateway') do |doc|
        doc.gateway_type gateway_type
        add_to_doc(doc, credentials, *credentials.keys)
      end
    end

    def add_credit_card_body(options)
      build_xml_request('payment_method') do |doc|
        add_to_doc(doc, options, :data, :retained, :email)
        doc.credit_card do
          add_to_doc(doc, options, :number, :month, :first_name, :last_name, :year,
                     :address1, :address2, :city, :state, :zip, :country, :phone_number)
        end
      end
    end

    def update_credit_card_body(options)
      build_xml_request('payment_method') do |doc|
        add_to_doc(doc, options, :email, :month, :first_name, :last_name, :year,
                   :address1, :address2, :city, :state, :zip, :country, :phone_number)
      end
    end

    def add_to_doc(doc, options, *attributes)
      attributes.each do |attr|
        doc.send(attr, options[attr.to_sym]) if options[attr.to_sym]
      end
    end

    def add_extra_options_for_basic_ops(doc, options)
      add_to_doc(doc, options, :order_id, :description, :ip, :merchant_name_descriptor,
                               :merchant_location_descriptor)
    end

    def build_xml_request(root)
      builder = Nokogiri::XML::Builder.new
      builder.__send__(root) do |doc|
        yield(doc)
      end
      builder.to_xml
    end

    def api_post(url, body)
      xml_doc = ssl_post(url, body, headers)
      Transaction.new_from(xml_doc)
    end

  end
end