require "json"

def decode_client_token(raw_client_token)
  decoded_client_token_string = Base64.decode64(raw_client_token)
  JSON.parse(decoded_client_token_string)
end

def nonce_for_new_payment_method(options)
  client = _initialize_client(options)
  response = client.add_payment_method(options)

  _nonce_from_response(response)
end

def _initialize_client(options)
  client_token_options = options.delete(:client_token_options) || {}
  raw_client_token = Braintree::ClientToken.generate(client_token_options)
  client_token = decode_client_token(raw_client_token)

  ClientApiHttp.new(Braintree::Configuration.instantiate,
    :authorization_fingerprint => client_token["authorizationFingerprint"],
    :shared_customer_identifier => "fake_identifier",
    :shared_customer_identifier_type => "testing",
  )
end

def _nonce_from_response(response)
  body = JSON.parse(response.body)

  if body["errors"] != nil
    raise body["errors"].inspect
  end

  if body.has_key?("paypalAccounts")
    body["paypalAccounts"][0]["nonce"]
  else
    body["creditCards"][0]["nonce"]
  end
end

def nonce_for_paypal_account(paypal_account_details)
  raw_client_token = Braintree::ClientToken.generate
  client_token = decode_client_token(raw_client_token)
  client = ClientApiHttp.new(Braintree::Configuration.instantiate,
    :authorization_fingerprint => client_token["authorizationFingerprint"],
  )

  response = client.create_paypal_account(paypal_account_details)
  body = JSON.parse(response.body)

  if body["errors"] != nil
    raise body["errors"].inspect
  end

  body["paypalAccounts"][0]["nonce"]
end

def generate_non_plaid_us_bank_account_nonce(account_number="1000000000")
  query = %{
    mutation TokenizeUsBankAccount($input: TokenizeUsBankAccountInput!) {
      tokenizeUsBankAccount(input: $input) {
        paymentMethod {
          id
        }
      }
    }
  }

  variables = {
    :input => {
      :usBankAccount => {
        :accountNumber => account_number,
        :routingNumber => "021000021",
        :accountType => "CHECKING",
        :individualOwner => {
          :firstName => "John",
          :lastName => "Doe",
        },
        :billingAddress => {
          :streetAddress => "123 Ave",
          :state => "CA",
          :city => "San Francisco",
          :zipCode => "94112"
        },
        :achMandate => "cl mandate text"
      }
    }
  }

  graphql_request = {
    :query => query,
    :variables => variables
  }

  json = _send_graphql_request(graphql_request)
  json["data"]["tokenizeUsBankAccount"]["paymentMethod"]["id"]
end

def generate_valid_plaid_us_bank_account_nonce
  query = %{
    mutation TokenizeUsBankLogin($input: TokenizeUsBankLoginInput!) {
      tokenizeUsBankLogin(input: $input) {
        paymentMethod {
          id
        }
      }
    }
  }

  variables = {
    :input => {
      :usBankLogin => {
        :publicToken => "good",
        :accountId => "plaid_account_id",
        :accountType => "CHECKING",
        :businessOwner => {
          :businessName => "PayPal, Inc.",
        },
        :billingAddress => {
          :streetAddress => "123 Ave",
          :state => "CA",
          :city => "San Francisco",
          :zipCode => "94112"
        },
        :achMandate => "cl mandate text"
      }
    }
  }

  graphql_request = {
    :query => query,
    :variables => variables
  }

  json = _send_graphql_request(graphql_request)
  json["data"]["tokenizeUsBankLogin"]["paymentMethod"]["id"]
end

def sample(arr)
  6.times.map { arr[rand(arr.length)] }.join
end

def generate_invalid_us_bank_account_nonce
  nonce_characters = "bcdfghjkmnpqrstvwxyz23456789".chars.to_a
  nonce = "tokenusbankacct_"
  nonce += 4.times.map { sample(nonce_characters) }.join("_")
  nonce + "_xxx"
end

def generate_us_bank_account_nonce_via_open_banking

  config = Braintree::Configuration.new(
    :environment => :development,
    :merchant_id => "integration_merchant_id",
    :public_key => "integration_public_key",
    :private_key => "integration_private_key",
  )

  request_body = {
    :account_details => {
      :account_number => "567891234",
      :account_type => "CHECKING",
      :classification => "PERSONAL",
      :tokenized_account => true,
      :last_4 => "1234"
    },
    :institution_details => {
      :bank_id => {
        :bank_code => "021000021",
        :country_code => "US"
      }
    },
    :account_holders => [
      {
        :ownership => "PRIMARY",
        :full_name => {
          :name => "Dan Schulman"
        },
        :name => {
          :given_name => "Dan",
          :surname => "Schulman",
          :full_name => "Dan Schulman"
        }
      }
    ]
  }

  graphql_base_url = config.graphql_base_url
  atmosphere_base_url = graphql_base_url.gsub("/graphql", "")
  url = "#{atmosphere_base_url}/v1/open-finance/tokenize-bank-account-details"

  uri = URI.parse(url)
  connection = Net::HTTP.new(uri.host, uri.port)

  if uri.scheme == "https"
    connection.use_ssl = true
    connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
    connection.ca_file = config.ca_file if config.ca_file
  end

  response = connection.start do |http|
    request = Net::HTTP::Post.new(uri.path)
    request["Content-Type"] = "application/json"
    request["Accept"] = "application/json"
    request["Braintree-Version"] = "2019-01-01"
    request["User-Agent"] = "Braintree Ruby Library #{Braintree::Version::String}"
    request["X-ApiVersion"] = config.api_version

    # Basic auth like Node.js
    auth_string = "#{config.public_key}:#{config.private_key}"
    request["Authorization"] = "Basic #{Base64.strict_encode64(auth_string)}"

    request.body = request_body.to_json

    http.request(request)
  end

  if response.code.to_i != 200
    raise "HTTP #{response.code}: #{response.body}"
  end

  result = JSON.parse(response.body)

  unless result["tenant_token"]
    raise "Open Banking tokenization failed: #{result.inspect}"
  end

  result["tenant_token"]
end

def _cosmos_post(token, url, payload)
  uri = URI::parse(url)
  connection = Net::HTTP.new(uri.host, uri.port)
  if uri.scheme == "https"
    connection.use_ssl = true
    connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
  end
  resp = connection.start do |http|
    request = Net::HTTP::Post.new(uri.path)
    request["Content-Type"] = "application/json"
    request["Braintree-Version"] = "2016-10-07"
    request["Authorization"] = "Bearer #{token}"
    request.body = payload.to_json
    http.request(request)
  end

  JSON.parse(resp.body)
end

def _send_graphql_request(graphql_request)
  raw_client_token = Braintree::ClientToken.generate
  client_token = decode_client_token(raw_client_token)
  uri = URI::parse("#{client_token["braintree_api"]["url"]}/graphql")
  connection = Net::HTTP.new(uri.host, uri.port)

  if uri.scheme == "https"
    connection.use_ssl = true
    connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
  end

  resp = connection.start do |http|
    request = Net::HTTP::Post.new(uri.path)
    request["Content-Type"] = "application/json"
    request["Braintree-Version"] = "2016-10-07"
    request["Authorization"] = "Bearer #{client_token["braintree_api"]["access_token"]}"
    request.body = graphql_request.to_json
    http.request(request)
  end

  JSON.parse(resp.body)
end

class ClientApiHttp
  attr_reader :config, :options

  def initialize(config, options)
    @config = config
    @options = options
  end

  def get(path)
    _http_do(Net::HTTP::Get, path)
  end

  def post(path, params)
    _http_do(Net::HTTP::Post, path, params.to_json)
  end

  def put(path, params)
    _http_do(Net::HTTP::Put, path, params.to_json)
  end

  def fingerprint=(fingerprint)
    @options[:authorization_fingerprint] = fingerprint
  end

  def _http_do(http_verb, path, body = nil)
    connection = Net::HTTP.new(@config.server, @config.port)
    connection.read_timeout = 60
    if @config.ssl?
      connection.use_ssl = true
      connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
      connection.ca_file = @config.ca_file
      connection.verify_callback = proc { |preverify_ok, ssl_context| _verify_ssl_certificate(preverify_ok, ssl_context) }
    end
    connection.start do |http|
      request = http_verb.new(path)
      request["X-ApiVersion"] = @config.api_version
      request["Content-Type"] = "application/json"
      request.body = body if body
      http.request(request)
    end
  rescue OpenSSL::SSL::SSLError
    raise Braintree::SSLCertificateError
  end

  def _verify_ssl_certificate(preverify_ok, ssl_context)
    if preverify_ok != true || ssl_context.error != 0
      err_msg = "SSL Verification failed -- Preverify: #{preverify_ok}, Error: #{ssl_context.error_string} (#{ssl_context.error})"
      @config.logger.error err_msg
      false
    else
      true
    end
  end

  def get_configuration
    encoded_fingerprint = Braintree::Util.url_encode(@options[:authorization_fingerprint])
    url = "/merchants/#{@config.merchant_id}/client_api/v1/configuration"
    url += "?authorizationFingerprint=#{encoded_fingerprint}"
    url += "&configVersion=3"

    get(url)
  end

  def get_payment_methods
    encoded_fingerprint = Braintree::Util.url_encode(@options[:authorization_fingerprint])
    url = "/merchants/#{@config.merchant_id}/client_api/v1/payment_methods?"
    url += "authorizationFingerprint=#{encoded_fingerprint}"
    url += "&sharedCustomerIdentifier=#{@options[:shared_customer_identifier]}"
    url += "&sharedCustomerIdentifierType=#{@options[:shared_customer_identifier_type]}"

    get(url)
  end

  def add_payment_method(params)
    fingerprint = @options[:authorization_fingerprint]
    params[:authorizationFingerprint] = fingerprint
    params[:sharedCustomerIdentifier] = @options[:shared_customer_identifier]
    params[:sharedCustomerIdentifierType] = @options[:shared_customer_identifier_type]

    payment_method_type = nil
    if params.has_key?(:paypal_account)
      payment_method_type = "paypal_accounts"
    else
      payment_method_type = "credit_cards"
    end

    post("/merchants/#{@config.merchant_id}/client_api/v1/payment_methods/#{payment_method_type}", params)
  end

  def create_credit_card(params)
    params = {:credit_card => params}
    params.merge!(
      :authorization_fingerprint => @options[:authorization_fingerprint],
      :shared_customer_identifier => @options[:shared_customer_identifier],
      :shared_customer_identifier_type => @options[:shared_customer_identifier_type],
    )

    post("/merchants/#{config.merchant_id}/client_api/v1/payment_methods/credit_cards", params)
  end

  def create_paypal_account(params)
    params = {:paypal_account => params}
    params.merge!(
      :authorization_fingerprint => @options[:authorization_fingerprint],
    )

    post("/merchants/#{config.merchant_id}/client_api/v1/payment_methods/paypal_accounts", params)
  end
end
