module Braintree
  # The TransparentRedirect module provides methods to build the tr_data param
  # that must be submitted when using the transparent redirect API. For more information
  # about transparent redirect, see (TODO).
  #
  # You must provide a redirect_url that the gateway will redirect the user to when the
  # action is complete.
  #
  #   tr_data = Braintree::TransparentRedirect.create_customer_data(
  #     :redirect_url => "http://example.com/redirect_back_to_merchant_site
  #   )
  #
  # In addition to the redirect_url, any data that needs to be protected from user tampering
  # should be included in the tr_data. For example, to prevent the user from tampering with the transaction
  # amount, include the amount in the tr_data.
  #
  #   tr_data = Braintree::TransparentRedirect.transaction_data(
  #     :redirect_url => "http://example.com/complete_transaction",
  #     :transaction => {:amount => "100.00"}
  #   )
  module TransparentRedirect
    TransparentRedirectKeys = [:redirect_url] # :nodoc:
    CreateCustomerSignature = TransparentRedirectKeys + [{:customer => Customer._create_signature}] # :nodoc:
    UpdateCustomerSignature = TransparentRedirectKeys + [:customer_id, {:customer => Customer._update_signature}] # :nodoc:
    TransactionSignature = TransparentRedirectKeys + [{:transaction => Transaction._create_signature}] # :nodoc:
    CreateCreditCardSignature = TransparentRedirectKeys + [{:credit_card => CreditCard._create_signature}] # :nodoc:
    UpdateCreditCardSignature = TransparentRedirectKeys + [:payment_method_token, {:credit_card => CreditCard._update_signature}] # :nodoc:

    module Kind
      CreateCustomer = "create_customer"
      UpdateCustomer = "update_customer"
      CreatePaymentMethod = "create_payment_method"
      UpdatePaymentMethod = "update_payment_method"
      CreateTransaction = "create_transaction"
    end

    def self.confirm(query_string)
      params = TransparentRedirect.parse_and_validate_query_string query_string
      confirmation_klass = {
        Kind::CreateCustomer => Braintree::Customer,
        Kind::UpdateCustomer => Braintree::Customer,
        Kind::CreatePaymentMethod => Braintree::CreditCard,
        Kind::UpdatePaymentMethod => Braintree::CreditCard,
        Kind::CreateTransaction => Braintree::Transaction
      }[params[:kind]]

      confirmation_klass._do_create("/transparent_redirect_requests/#{params[:id]}/confirm")
    end

    # Returns the tr_data string for creating a credit card.
    def self.create_credit_card_data(params)
      Util.verify_keys(CreateCreditCardSignature, params)
      params[:kind] = Kind::CreatePaymentMethod
      _data(params)
    end

    # Returns the tr_data string for creating a customer.
    def self.create_customer_data(params)
      Util.verify_keys(CreateCustomerSignature, params)
      params[:kind] = Kind::CreateCustomer
      _data(params)
    end

    def self.parse_and_validate_query_string(query_string) # :nodoc:
      params = Util.symbolize_keys(Util.parse_query_string(query_string))
      query_string_without_hash = query_string[/(.*)&hash=.*/, 1]

      if params[:http_status] == nil
        raise UnexpectedError, "expected query string to have an http_status param"
      elsif params[:http_status] != '200'
        Util.raise_exception_for_status_code(params[:http_status], params[:bt_message])
      end

      if _hash(query_string_without_hash) == params[:hash]
        params
      else
        raise ForgedQueryString
      end
    end

    # Returns the tr_data string for creating a transaction.
    def self.transaction_data(params)
      Util.verify_keys(TransactionSignature, params)
      params[:kind] = Kind::CreateTransaction
      transaction_type = params[:transaction] && params[:transaction][:type]
      unless %w[sale credit].include?(transaction_type)
        raise ArgumentError, "expected transaction[type] of sale or credit, was: #{transaction_type.inspect}"
      end
      _data(params)
    end

    # Returns the tr_data string for updating a credit card.
    # The payment_method_token of the credit card to update is required.
    #
    #   tr_data = Braintree::TransparentRedirect.update_credit_card_data(
    #     :redirect_url => "http://example.com/redirect_here",
    #     :payment_method_token => "token123"
    #   )
    def self.update_credit_card_data(params)
      Util.verify_keys(UpdateCreditCardSignature, params)
      unless params[:payment_method_token]
        raise ArgumentError, "expected params to contain :payment_method_token of payment method to update"
      end
      params[:kind] = Kind::UpdatePaymentMethod
      _data(params)
    end

    # Returns the tr_data string for updating a customer.
    # The customer_id of the customer to update is required.
    #
    #   tr_data = Braintree::TransparentRedirect.update_customer_data(
    #     :redirect_url => "http://example.com/redirect_here",
    #     :customer_id => "customer123"
    #   )
    def self.update_customer_data(params)
      Util.verify_keys(UpdateCustomerSignature, params)
      unless params[:customer_id]
        raise ArgumentError, "expected params to contain :customer_id of customer to update"
      end
      params[:kind] = Kind::UpdateCustomer
      _data(params)
    end

    # Returns the URL to which Transparent Redirect Requests should be posted
    def self.url
      "#{Braintree::Configuration.base_merchant_url}/transparent_redirect_requests"
    end

    def self._data(params) # :nodoc:
      raise ArgumentError, "expected params to contain :redirect_url" unless params[:redirect_url]
      tr_data_segment = Util.hash_to_query_string(params.merge(
        :api_version => Configuration::API_VERSION,
        :time => Time.now.utc.strftime("%Y%m%d%H%M%S"),
        :public_key => Configuration.public_key
      ))
      tr_data_hash = _hash(tr_data_segment)
      "#{tr_data_hash}|#{tr_data_segment}"
    end

    def self._hash(string) # :nodoc:
      ::Braintree::Digest.hexdigest(string)
    end
  end
end