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

describe InsertPayout do
  let(:bank_name) {'CHASE *1234'}
  let(:supporter) {force_create(:supporter)}
  let(:user_email) {'uzr@example.com'}
  let(:user_ip) {'8.8.8.8'}

  describe '.with_stripe' do
    describe 'param validation' do
      it 'basic param validation' do
        expect {InsertPayout.with_stripe(nil, nil, nil)}.to(raise_error {|error|
          expect(error).to be_a ParamValidation::ValidationError
          expect_validation_errors(error.data, [
              {key: :np_id, name: :required},
              {key: :np_id, name: :is_integer},
              {key: :stripe_account_id, name: :required},
              {key: :stripe_account_id, name: :not_blank},
              {key: :email, name: :required},
              {key: :email, name: :not_blank},
              {key: :user_ip, name: :required},
              {key: :user_ip, name: :not_blank},
              {key: :bank_name, name: :required},
              {key: :bank_name, name: :not_blank},
          ])
        })

      end

      it 'validates nonprofit' do
        expect {InsertPayout.with_stripe(666, {:stripe_account_id => 'valid', :email => 'valid', user_ip: 'valid', bank_name: 'valid'}, nil)}.to(raise_error {|error|
          expect(error).to be_a ParamValidation::ValidationError
          expect_validation_errors(error.data, [{key: :np_id}])
        })
      end
    end

    context 'when valid' do
      let(:stripe_helper) {StripeMock.create_test_helper}

      before(:each) {
        Timecop.freeze(2020, 5, 4)
        StripeMock.start
      }

      after {
        StripeMock.stop
        Timecop.return
      }

      it 'handles no charges to payout' do
        np = force_create(:nonprofit)
        expect {InsertPayout.with_stripe(np.id, {:stripe_account_id => 'valid', :email => 'valid', user_ip: 'valid', bank_name: 'valid'}, nil)}.to(raise_error {|error|
          expect(error).to be_a ArgumentError
          expect(error.message).to eq "No payments are available for disbursal on this account."
        })
      end
      let(:user) {force_create(:user)}

      # Test one basic charge, one charge with a partial refund, and one charge with a full refund


      # refunded payment
      # disputed payment

      # Charge which was after given date
      #
      # Already paid out charge
      # Already paid out dispute
      # already paid out refund

      context 'no date provided' do
        include_context 'payments for a payout' do
          let(:np) {force_create(:nonprofit, :stripe_account_id => Stripe::Account.create()['id'], vetted: true)}
          let(:date_for_marking) {Time.now}
          let(:ba) {
            InsertBankAccount.with_stripe(np, user, {stripe_bank_account_token: StripeMock.generate_bank_token(), name: bank_name})}
        end

        before(:each) {
          ba
        }
        let(:expected_totals) {{gross_amount: 5500, fee_total: -1200, net_amount: 4300, count: 8}}
        it 'works without a date provided' do
          stripe_transfer_id = nil
          expect(Stripe::Transfer).to receive(:create).with({amount: expected_totals[:net_amount],
                                                             currency: 'usd',
                                                             recipient: 'self'
                                                            }, {
                                                                stripe_account: np.stripe_account_id})
                                          .and_wrap_original {|m, *args|
                                            i = m.call(*args)
                                            stripe_transfer_id = i['id'];
                                            i
                                          }
          all_payments
          result = InsertPayout.with_stripe(np.id, {stripe_account_id: np.stripe_account_id,
                                                    email: user_email,
                                                    user_ip: user_ip,
                                                    bank_name: bank_name
          })

          expected_result = {
              net_amount: expected_totals[:net_amount],
              nonprofit_id: np.id,
              status: 'pending',
              fee_total: expected_totals[:fee_total],
              gross_amount: expected_totals[:gross_amount],
              email: user_email,
              count: expected_totals[:count],
              stripe_transfer_id: stripe_transfer_id,
              user_ip: user_ip,
              ach_fee: 0,
              bank_name: bank_name,
              updated_at: Time.now,
              created_at: Time.now
          }.with_indifferent_access
          expect(Payout.count).to eq 1
          resulted_payout = Payout.first
          expect(result.with_indifferent_access).to eq expected_result.merge(id: resulted_payout.id)

          empty_db_attributes = {manual: nil, scheduled: nil, failure_message: nil}
          expect(resulted_payout.attributes.with_indifferent_access).to eq expected_result.merge(id: resulted_payout.id).merge(empty_db_attributes)

          # validate which charges are makred
          @expect_marked[:charges].each {|c|
            c.reload()
            expect(c.status).to eq 'disbursed'
          }


          # validate which refunds are marked

          @expect_marked[:refunds].each {|r|
            r.reload()
            expect(r.disbursed).to eq true
          }

          # validate which disputes are marked
          @expect_marked[:disputes].each {|d|
            d.reload
            expect(d.status).to eq 'lost_and_paid'
          }
          # validate payment payout records

          expect(resulted_payout.payments.pluck('payments.id')).to eq @expect_marked[:payouts_records].collect {|i| i.id}
        end

        it 'fails properly when Stripe payout call fails' do
          StripeMock.prepare_error(Stripe::StripeError.new("Payout failed"), :new_transfer)

          all_payments
          result = InsertPayout.with_stripe(np.id, {stripe_account_id: np.stripe_account_id,
                                                    email: user_email,
                                                    user_ip: user_ip,
                                                    bank_name: bank_name
          })

          expected_result = {
              net_amount: expected_totals[:net_amount],
              nonprofit_id: np.id,
              status: 'failed',
              fee_total: expected_totals[:fee_total],
              gross_amount: expected_totals[:gross_amount],
              email: user_email,
              count: expected_totals[:count],
              stripe_transfer_id: nil,
              user_ip: user_ip,
              ach_fee: 0,
              bank_name: bank_name,
              updated_at: Time.now,
              created_at: Time.now
          }.with_indifferent_access

          expect(Payout.count).to eq 1
          resulted_payout = Payout.first
          expect(result.with_indifferent_access).to eq expected_result.merge(id: resulted_payout.id)

          empty_db_attributes = {manual: nil, scheduled: nil, failure_message: 'Payout failed', }
          expect(resulted_payout.attributes.with_indifferent_access).to eq expected_result.merge(id: resulted_payout.id).merge(empty_db_attributes)

          # validate which charges are makred
          @expect_marked[:charges].each {|c|
            c.reload()
            expect(c.status).to eq 'available'
          }


          # validate which refunds are marked

          @expect_marked[:refunds].each {|r|
            r.reload()
            expect(r.disbursed).to be_falsey
          }

          # validate which disputes are marked
          @expect_marked[:disputes].each {|d|
            d.reload
            expect(d.status).to eq 'lost'
          }
          # validate payment payout records

          expect(resulted_payout.payments.count).to eq 0
        end
      end

      context 'previous date provided' do
        include_context 'payments for a payout' do
          let(:np) {force_create(:nonprofit, :stripe_account_id => Stripe::Account.create()['id'], vetted: true)}
          let(:date_for_marking) {Time.now - 1.day}
          let(:ba) {InsertBankAccount.with_stripe(np, user, {stripe_bank_account_token: StripeMock.generate_bank_token(), name: bank_name})}
        end
        before(:each) {
            ba
        }

        let(:expected_totals) {{gross_amount: 3500, fee_total: -800, net_amount: 2700, count: 7}}
        it 'works with date provided' do
          stripe_transfer_id = nil
          expect(Stripe::Transfer).to receive(:create).with({amount: expected_totals[:net_amount],
                                                             currency: 'usd',
                                                             recipient: 'self'
                                                            }, {
                                                                stripe_account: np.stripe_account_id})
                                          .and_wrap_original {|m, *args|
                                            i = m.call(*args)
                                            stripe_transfer_id = i['id'];
                                            i
                                          }
          all_payments
          result = InsertPayout.with_stripe(np.id, {stripe_account_id: np.stripe_account_id,
                                                    email: user_email,
                                                    user_ip: user_ip,
                                                    bank_name: bank_name
          }, {date: Time.now - 1.day})

          expected_result = {
              net_amount: expected_totals[:net_amount],
              nonprofit_id: np.id,
              status: 'pending',
              fee_total: expected_totals[:fee_total],
              gross_amount: expected_totals[:gross_amount],
              email: user_email,
              count: expected_totals[:count],
              stripe_transfer_id: stripe_transfer_id,
              user_ip: user_ip,
              ach_fee: 0,
              bank_name: bank_name,
              updated_at: Time.now,
              created_at: Time.now
          }.with_indifferent_access
          expect(Payout.count).to eq 1
          resulted_payout = Payout.first
          expect(result.with_indifferent_access).to eq expected_result.merge(id: resulted_payout.id)

          empty_db_attributes = {manual: nil, scheduled: nil, failure_message: nil}
          expect(resulted_payout.attributes.with_indifferent_access).to eq expected_result.merge(id: resulted_payout.id).merge(empty_db_attributes)

          # validate which charges are makred
          @expect_marked[:charges].each {|c|
            c.reload()
            expect(c.status).to(eq('disbursed'), "#{c.attributes.to_s}")
          }

          # validate which refunds are marked
          @expect_marked[:refunds].each {|r|
            r.reload()
            expect(r.disbursed).to eq true
          }

          # validate which disputes are marked
          @expect_marked[:disputes].each {|d|
            d.reload
            expect(d.status).to eq 'lost_and_paid'
          }
        end

        it 'fails properly when Stripe payout call fails' do
          StripeMock.prepare_error(Stripe::StripeError.new("Payout failed"), :new_transfer)

          all_payments
          result = InsertPayout.with_stripe(np.id, {stripe_account_id: np.stripe_account_id,
                                                    email: user_email,
                                                    user_ip: user_ip,
                                                    bank_name: bank_name
          }, {date: Time.now - 1.day})

          expected_result = {
              net_amount: expected_totals[:net_amount],
              nonprofit_id: np.id,
              status: 'failed',
              fee_total: expected_totals[:fee_total],
              gross_amount: expected_totals[:gross_amount],
              email: user_email,
              count: expected_totals[:count],
              stripe_transfer_id: nil,
              user_ip: user_ip,
              ach_fee: 0,
              bank_name: bank_name,
              updated_at: Time.now,
              created_at: Time.now
          }.with_indifferent_access

          expect(Payout.count).to eq 1
          resulted_payout = Payout.first
          expect(result.with_indifferent_access).to eq expected_result.merge(id: resulted_payout.id)

          empty_db_attributes = {manual: nil, scheduled: nil, failure_message: 'Payout failed', }
          expect(resulted_payout.attributes.with_indifferent_access).to eq expected_result.merge(id: resulted_payout.id).merge(empty_db_attributes)

          # validate which charges are makred
          @expect_marked[:charges].each {|c|
            c.reload()
            expect(c.status).to eq 'available'
          }

          # validate which refunds are marked
          @expect_marked[:refunds].each {|r|
            r.reload()
            expect(r.disbursed).to be_falsey
          }

          # validate which disputes are marked
          @expect_marked[:disputes].each {|d|
            d.reload
            expect(d.status).to eq 'lost'
          }
          # validate payment payout records

          expect(resulted_payout.payments.count).to eq 0
        end
      end
    end
  end
end