# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
require 'rails_helper'

describe InsertBankAccount do
  let(:stripe_helper) { StripeMock.create_test_helper }
  before(:each) {
    Timecop.freeze(2020,5,4)
    StripeMock.start
  }
  after(:each) {
    StripeMock.stop
    Timecop.return
  }

  let(:nonprofit) {force_create(:nonprofit) }
  let(:user) { force_create(:user, :email => 'x@example.com')}


  describe '.with_stripe' do
    describe 'param validation' do
      it 'validates np and user' do
        expect { InsertBankAccount.with_stripe(nil, nil, nil)}.to(raise_error{|error|
          expect(error).to be_a ParamValidation::ValidationError
          expect_validation_errors(error.data, [{:key => :nonprofit, :name => :required},
                                                {:key => :nonprofit, :name => :is_a},
                                                {:key => :user, :name => :required},
                                                {:key => :user, :name => :is_a}])

        })

        expect { InsertBankAccount.with_stripe(1, 2, nil)}.to(raise_error{|error|
          expect(error).to be_a ParamValidation::ValidationError
          expect_validation_errors(error.data, [
              {:key => :nonprofit, :name => :is_a},
              {:key => :user, :name => :is_a}])

        })
      end

      it 'validate stripe_bank_account_token' do
        expect { InsertBankAccount.with_stripe(nonprofit, user, nil)}.to(raise_error{|error|
          expect(error).to be_a ParamValidation::ValidationError
          expect_validation_errors(error.data, [{
                                                    :key => :stripe_bank_account_token,
                                                    :name => :required
                                                },
                                                {
                                                    :key => :stripe_bank_account_token,
                                                    :name => :not_blank
                                                }])
        })
      end

      it 'validates whether vetted' do
        expect { InsertBankAccount.with_stripe(nonprofit, user, {:stripe_bank_account_token => 'blah'})}.to(raise_error{|error|
          expect(error).to be_a ArgumentError
          expect(error.message).to include("vetted")
        })
      end

    end

    describe 'exceptions in main function' do
      before (:each) { nonprofit.vetted = true}
      it 'StripeAccount.find_or_create fails' do
        expect(StripeAccount).to receive(:find_or_create).and_raise(StandardError.new)

        expect { InsertBankAccount.with_stripe(nonprofit, user, {:stripe_bank_account_token => 'blah'})}.to(raise_error{|error|
          expect(error).to be_a StandardError
        })
      end

      it 'Stripe::Account.retrieve fails' do
        expect(StripeAccount).to receive(:find_or_create).and_return("account_id")
        StripeMock.prepare_error(Stripe::StripeError.new("some error happened"), :get_account )

        expect { InsertBankAccount.with_stripe(nonprofit, user, {:stripe_bank_account_token => 'blah'})}.to(raise_error{|error|
          expect(error).to be_a Stripe::StripeError
        })
      end
    end

    describe 'works with account retrieval' do
      before (:each) { nonprofit.vetted = true; nonprofit.save!}
      let(:stripe_acct) {Stripe::Account.create(managed: true, country: 'US', display_name: "test_display_name")}
      let(:stripe_bank_account_token) { StripeMock.generate_bank_token(country: "US", routing_number: "110000000", account_number: "000123456789") }

      it 'sets failure message when external_account create fails' do
        expect(Stripe::Account).to receive(:retrieve).and_return(stripe_acct)
        StripeMock.prepare_error(Stripe::StripeError.new("hmm"), :create_external_account)
        expect { InsertBankAccount.with_stripe(nonprofit, user, {:stripe_bank_account_token => stripe_bank_account_token}) }.to raise_error {|error|
          expect(error).to be_a ArgumentError
          expect(error.message).to eq 'Failed to connect the bank account: #<Stripe::StripeError: hmm>'
        }
      end

      it 'works with external account creation' do
        expect(Stripe::Account).to receive(:retrieve).and_return(stripe_acct)

        result = InsertBankAccount.with_stripe(nonprofit, user, {:stripe_bank_account_token => stripe_bank_account_token})
        expected = {:email => user.email,
                    :stripe_bank_account_token => stripe_bank_account_token,
                    :pending_verification => true,
                    created_at: Time.now(),
                    updated_at: Time.now(),
                    status: nil, #doesn't seem to be used
                    id: 1,
                    deleted: nil,
                    account_number: nil, #doesn't seem to be used
                    nonprofit_id: nonprofit.id,
                    bank_name: nil
        }.with_indifferent_access
        expect(result.attributes.with_indifferent_access.except(:confirmation_token, :stripe_bank_account_id, :name)).to eq expected
        expect(result[:confirmation_token]).to_not be_blank
        expect(result[:stripe_bank_account_id]).to_not be_blank
        expect(result[:name]).to_not be_blank
      end
    end

    describe 'handles replacing the old accounts' do
      before (:each) { nonprofit.vetted = true; nonprofit.save!
        old_bank_account_false
        old_bank_account_nil
        old_bank_account_true
      }
      let(:stripe_acct) {Stripe::Account.create(managed: true, country: 'US', display_name: "test_display_name")}
      let(:stripe_bank_account_token) { StripeMock.generate_bank_token(country: "US", routing_number: "110000000", account_number: "000123456789") }

      let(:old_bank_account_nil) { force_create(:bank_account, nonprofit: nonprofit, deleted: nil)}
      let(:old_bank_account_false) { force_create(:bank_account, nonprofit: nonprofit, deleted: false)}
      let(:old_bank_account_true) { force_create(:bank_account, nonprofit: nonprofit, deleted: true)}



      it 'works with external account creation' do
        expect(Stripe::Account).to receive(:retrieve).and_return(stripe_acct)

        result = InsertBankAccount.with_stripe(nonprofit, user, {:stripe_bank_account_token => stripe_bank_account_token})
        expected = {:email => user.email,
                    :stripe_bank_account_token => stripe_bank_account_token,
                    :pending_verification => true,
                    created_at: Time.now(),
                    updated_at: Time.now(),
                    status: nil, #doesn't seem to be used
                    id: result['id'],
                    deleted: nil,
                    account_number: nil, #doesn't seem to be used
                    nonprofit_id: nonprofit.id,
                    bank_name: nil
        }.with_indifferent_access
        expect(result.attributes.with_indifferent_access.except(:confirmation_token, :stripe_bank_account_id, :name)).to eq expected
        expect(result[:confirmation_token]).to_not be_blank
        expect(result[:stripe_bank_account_id]).to_not be_blank
        expect(result[:name]).to_not be_blank


        expect(nonprofit.bank_account).to eq result
        expect(BankAccount.where('nonprofit_id = ?', nonprofit.id).count).to eq 4
        expect(BankAccount.where('nonprofit_id = ? and deleted = true', nonprofit.id).count).to eq 3
      end
    end
  end
end