require 'spec_helper'
require 'pry'

describe Spree::Gateway::BraintreeGateway do
  let!(:country) { create(:country, name: 'United States', iso_name: 'UNITED STATES', iso3: 'USA', iso: 'US', numcode: 840) }
  let!(:state) { create(:state, name: 'Maryland', abbr: 'MD', country: country) }
  let!(:address) { create(:address, firstname: 'John', lastname: 'Doe', address1: '1234 My Street', address2: 'Apt 1', city: 'Washington DC', zipcode: '20123', phone: '(555)555-5555', state: state, country: country) }

  before do
    Spree::Gateway.update_all(active: false)
    @gateway = Spree::Gateway::BraintreeGateway.create!(name: 'Braintree Gateway', active: true)
    @gateway.preferences = {
      environment: 'sandbox',
      merchant_id: 'zbn5yzq9t7wmwx42',
      public_key: 'ym9djwqpkxbv3xzt',
      private_key: '4ghghkyp2yy6yqc8'
    }
    @gateway.save!

    with_payment_profiles_off do
      order = create(:order_with_totals, bill_address: address, ship_address: address)
      order.update!

      # Use a valid CC from braintree sandbox: https://www.braintreepayments.com/docs/ruby/reference/sandbox

      @credit_card = create(:credit_card,
        verification_value: '123',
        number:             '5555555555554444',
        month:              9,
        year:               Time.now.year + 1,
        name:               'John Doe',
        cc_type:            'mastercard')

      @payment = create(:payment, source: @credit_card, order: order, payment_method: @gateway, amount: 10.00)
    end
  end

  describe 'payment profile creation' do
    before do
      order = create(:order_with_totals, bill_address: address, ship_address: address)
      order.update!

      @credit_card = create(:credit_card,
        verification_value: '123',
        number:             '5555555555554444',
        month:              9,
        year:               Time.now.year + 1,
        name:               'John Doe',
        cc_type:            'mastercard')

      @payment = create(:payment, source: @credit_card, order: order, payment_method: @gateway, amount: 10.00)
    end

    context 'when a credit card is created' do
      it 'it has the address associated on the remote payment profile' do
        remote_customer = @gateway.provider.instance_variable_get(:@braintree_gateway).customer.find(@credit_card.gateway_customer_profile_id)
        remote_address = remote_customer.addresses.first rescue nil
        expect(remote_address).not_to be_nil
        expect(remote_address.street_address).to eq(address.address1)
        expect(remote_address.extended_address).to eq(address.address2)
        expect(remote_address.locality).to eq(address.city)
        expect(remote_address.region).to eq(address.state.name)
        expect(remote_address.country_code_alpha2).to eq(address.country.iso)
        expect(remote_address.postal_code).to eq(address.zipcode)
      end
    end

  end

  describe 'payment profile failure' do
    before do
      order = create(:order_with_totals, bill_address: address, ship_address: address)
      order.update!

      @credit_card = create(:credit_card,
        verification_value: '123',
        number:             '5105105105105100',
        month:              9,
        year:               Time.now.year + 1,
        name:               'John Doe',
        cc_type:            'mastercard')
    end

    it 'should fail creation' do
      expect{ create(:payment, source: @credit_card, order: order, payment_method: @gateway, amount: 10.00) }.to raise_error
    end

  end

  describe 'merchant_account_id' do
    before do
      @gateway.preferences[:merchant_account_id] = merchant_account_id
    end

    context 'with merchant_account_id empty' do
      let(:merchant_account_id) { '' }

      it 'does not be present in options' do
        expect(@gateway.options.keys.include?(:merchant_account_id)).to be false
      end
    end

    context 'with merchant_account_id set on gateway' do
      let(:merchant_account_id) { 'test' }

      it 'have a perferred_merchant_account_id' do
        expect(@gateway.preferred_merchant_account_id).to eq merchant_account_id
      end

      it 'have a preferences[:merchant_account_id]' do
        expect(@gateway.preferences.keys.include?(:merchant_account_id)).to be true
      end

      it 'is present in options' do
        expect(@gateway.options.keys.include?(:merchant_account_id)).to be true
      end
    end
  end

  context '.provider_class' do
    it 'is a BraintreeBlue gateway' do
      expect(@gateway.provider_class).to eq ::ActiveMerchant::Billing::BraintreeBlueGateway
    end
  end

  context '.payment_profiles_supported?' do
    it 'return true' do
      expect(@gateway.payment_profiles_supported?).to be true
    end
  end

  context 'preferences' do
    it 'does not include server + test_mode' do
      expect { @gateway.preferences.fetch(:server) }.to raise_error(StandardError)
    end
  end

  describe 'authorize' do
    context "the credit card has a token" do
      before(:each) do
        @credit_card.update_attributes(gateway_payment_profile_id: 'test')
      end

      it 'calls provider#authorize using the gateway_payment_profile_id' do
        expect(@gateway.provider).to receive(:authorize).with(500, 'test', { payment_method_token: true } )
        @gateway.authorize(500, @credit_card)
      end
    end

    context "the given credit card does not have a token" do
      context "the credit card has a customer profile id" do
        before(:each) do
          @credit_card.update_attributes(gateway_customer_profile_id: '12345')
        end

        it 'calls provider#authorize using the gateway_customer_profile_id' do
          expect(@gateway.provider).to receive(:authorize).with(500, '12345', {})
          @gateway.authorize(500, @credit_card)
        end
      end

      context "no customer profile id" do
        it 'calls provider#authorize with the credit card object' do
          expect(@gateway.provider).to receive(:authorize).with(500, @credit_card, {})
          @gateway.authorize(500, @credit_card)
        end
      end
    end

    it 'return a success response with an authorization code' do
      result = @gateway.authorize(500, @credit_card)

      expect(result.success?).to be true
      expect(result.authorization).to match /\A\w{6}\z/
      expect(Braintree::Transaction::Status::Authorized).to eq Braintree::Transaction.find(result.authorization).status
    end

    shared_examples 'a valid credit card' do
      it 'work through the spree payment interface' do
        Spree::Config.set auto_capture: false
        expect(@payment.log_entries.size).to eq(0)

        @payment.process!
        expect(@payment.log_entries.size).to eq(1)
        expect(@payment.response_code).to match /\A\w{6}\z/
        expect(@payment.state).to eq 'pending'

        transaction = ::Braintree::Transaction.find(@payment.response_code)
        expect(transaction.status).to eq Braintree::Transaction::Status::Authorized

        card_number = @credit_card.number[0..5] + '******' + @credit_card.number[-4..-1]
        expect(transaction.credit_card_details.masked_number).to eq card_number
        expect(transaction.credit_card_details.expiration_date).to eq "09/#{Time.now.year + 1}"
        expect(transaction.customer_details.first_name).to eq 'John'
        expect(transaction.customer_details.last_name).to eq 'Doe'
      end
    end

    context 'when the card is a mastercard' do
      before do
        @credit_card.number = '5555555555554444'
        @credit_card.cc_type = 'mastercard'
        @credit_card.save
      end

      it_behaves_like 'a valid credit card'
    end

    context 'when the card is a visa' do
      before do
        @credit_card.number = '4111111111111111'
        @credit_card.cc_type = 'visa'
        @credit_card.save
      end

      it_behaves_like 'a valid credit card'
    end

    context 'when the card is an amex' do
      before do
        @credit_card.number = '378282246310005'
        @credit_card.verification_value = '1234'
        @credit_card.cc_type = 'amex'
        @credit_card.save
      end

      it_behaves_like 'a valid credit card'
    end

    context 'when the card is a JCB' do
      before do
        @credit_card.number = '3530111333300000'
        @credit_card.cc_type = 'jcb'
        @credit_card.save
      end

      it_behaves_like 'a valid credit card'
    end

    context 'when the card is a diners club' do
      before do
        @credit_card.number = '36050000000003'
        @credit_card.cc_type = 'dinersclub'
        @credit_card.save
      end

      it_behaves_like 'a valid credit card'
    end
  end

  describe 'capture' do
    it 'do capture a previous authorization' do
      @payment.process!
      expect(@payment.log_entries.size).to eq(1)
      expect(@payment.response_code).to match /\A\w{6}\z/

      transaction = ::Braintree::Transaction.find(@payment.response_code)
      expect(transaction.status).to eq Braintree::Transaction::Status::Authorized

      capture_result = @gateway.capture(@payment.amount, @payment.response_code)
      expect(capture_result.success?).to be true

      transaction = ::Braintree::Transaction.find(@payment.response_code)
      expect(transaction.status).to eq Braintree::Transaction::Status::SubmittedForSettlement
    end

    it 'raise an error if capture fails using spree interface' do
      Spree::Config.set(auto_capture: false)
      expect(@payment.log_entries.size).to eq(0)

      @payment.process!
      expect(@payment.log_entries.size).to eq(1)

      transaction = ::Braintree::Transaction.find(@payment.response_code)
      expect(transaction.status).to eq Braintree::Transaction::Status::Authorized

      @payment.capture! # as done in PaymentsController#fire
      transaction = ::Braintree::Transaction.find(@payment.response_code)
      expect(transaction.status).to eq Braintree::Transaction::Status::SubmittedForSettlement
      expect(@payment.completed?).to be true
    end
  end

  context 'purchase' do
    it 'return a success response with an authorization code' do
      result =  @gateway.purchase(500, @credit_card)
      expect(result.success?).to be true
      expect(result.authorization).to match /\A\w{6}\z/
      expect(Braintree::Transaction::Status::SubmittedForSettlement).to eq Braintree::Transaction.find(result.authorization).status
    end

    it 'work through the spree payment interface with payment profiles' do
      purchase_using_spree_interface
      transaction = ::Braintree::Transaction.find(@payment.response_code)
      expect(transaction.credit_card_details.token).not_to be_nil
    end

    it 'work through the spree payment interface without payment profiles' do
      with_payment_profiles_off do
        purchase_using_spree_interface(false)
        transaction = ::Braintree::Transaction.find(@payment.response_code)
        expect(transaction.credit_card_details.token).to be_nil
      end
    end
  end

  context 'credit' do
    it 'work through the spree interface' do
      @payment.amount += 100.00
      purchase_using_spree_interface
      skip "Braintree does not provide a way to settle a transaction manually: https://twitter.com/braintree/status/446099537224933376"
      credit_using_spree_interface
    end
  end

  context 'void' do
    before do
      Spree::Config.set(auto_capture: true)
    end

    it 'work through the spree credit_card / payment interface' do
      expect(@payment.log_entries.size).to eq(0)
      @payment.process!

      expect(@payment.log_entries.size).to eq(1)
      expect(@payment.response_code).to match /\A\w{6}\z/

      transaction = Braintree::Transaction.find(@payment.response_code)
      expect(transaction.status).to eq Braintree::Transaction::Status::SubmittedForSettlement

      @payment.void_transaction!
      transaction = Braintree::Transaction.find(transaction.id)
      expect(transaction.status).to eq Braintree::Transaction::Status::Voided
    end
  end

  context 'update_card_number' do
    it 'passes through gateway_payment_profile_id' do
      credit_card = { 'token' => 'testing', 'last_4' => '1234', 'masked_number' => '555555******4444' }
      @gateway.update_card_number(@payment.source, credit_card)
      expect(@payment.source.gateway_payment_profile_id).to eq 'testing'
    end
  end

  def credit_using_spree_interface
    expect(@payment.log_entries.size).to eq(1)
    @payment.credit!
    expect(@payment.log_entries.size).to eq(2)

    # Let's get the payment record associated with the credit
    @payment = @order.payments.last
    expect(@payment.response_code).to match /\A\w{6}\z/

    transaction = ::Braintree::Transaction.find(@payment.response_code)
    expect(transaction.type).to eq Braintree::Transaction::Type::Credit
    expect(transaction.status).to eq Braintree::Transaction::Status::SubmittedForSettlement
    expect(transaction.credit_card_details.masked_number).to eq '555555******4444'
    expect(transaction.credit_card_details.expiration_date).to eq "09/#{Time.now.year + 1}"
    expect(transaction.customer_details.first_name).to eq 'John'
    expect(transaction.customer_details.last_name).to eq 'Doe'
  end

  def purchase_using_spree_interface(profile=true)
    Spree::Config.set(auto_capture: true)
    @payment.send(:create_payment_profile) if profile
    @payment.log_entries.size == 0
    @payment.process! # as done in PaymentsController#create
    @payment.log_entries.size == 1
    expect(@payment.response_code).to match /\A\w{6}\z/
    expect(@payment.state).to eq 'completed'

    transaction = ::Braintree::Transaction.find(@payment.response_code)
    expect(Braintree::Transaction::Status::SubmittedForSettlement).to eq transaction.status
    expect(transaction.credit_card_details.masked_number).to eq '555555******4444'
    expect(transaction.credit_card_details.expiration_date).to eq "09/#{Time.now.year + 1}"
    expect(transaction.customer_details.first_name).to eq 'John'
    expect(transaction.customer_details.last_name).to eq 'Doe'
  end

  def with_payment_profiles_off(&block)
    Spree::Gateway::BraintreeGateway.class_eval do
      def payment_profiles_supported?
        false
      end
    end
    yield
  ensure
    Spree::Gateway::BraintreeGateway.class_eval do
      def payment_profiles_supported?
        true
      end
    end
  end
end
