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

describe UpdateDonation do

  before {
    Timecop.freeze(2020, 2, 3)
  }

  after {
    Timecop.return
  }
  let(:np) {force_create(:nonprofit)}
  let(:supporter) {force_create(:supporter, nonprofit: np)}
  let(:donation) {force_create(:donation, nonprofit: np,
                               dedication: initial_dedication,
                               comment: initial_comment,
                               designation: initial_designation,
                               amount: initial_amount,
                               date: initial_date,
                               supporter: supporter)}

  let(:payment) {force_create(:payment, nonprofit: np, donation: donation,
                               towards: initial_designation,
                               date: initial_date,
                               gross_amount: initial_amount,
                               fee_total: initial_fee,
                               net_amount: initial_amount - initial_fee,
                               supporter: supporter
  )}
  let(:offsite_payment) {force_create(:offsite_payment, payment: payment, nonprofit: np, donation: donation,
                                      check_number: initial_check_number,
                                      gross_amount: initial_amount,
                                      date: initial_date,
                                      supporter: supporter)}

  let(:payment2) {force_create(:payment, nonprofit: np, donation: donation,
                               towards: initial_designation,
                               date: payment2_date,
                               gross_amount: initial_amount,
                               fee_total: initial_fee,
                               net_amount: initial_amount - initial_fee
  )}
  let(:campaign) {force_create(:campaign, nonprofit: np)}
  let(:event) {force_create(:event, nonprofit: np)}
  let(:other_campaign) {force_create(:campaign)}
  let(:other_event) {force_create(:event)}

  let(:initial_date) {Date.new(2020, 4, 5).to_time}
  let(:initial_dedication) {"initial dedication"}
  let(:initial_comment) {"comment"}
  let(:initial_amount) {4000}
  let(:initial_designation) {"designation"}
  let(:initial_fee) {555}
  let(:initial_check_number) {"htoajmioeth"}


  let(:new_date_input) { '2020-05-05'}
  let(:new_date) {Date.new(2020, 5, 5)}
  let(:new_dedication) {"new dedication"}
  let(:new_comment) {"new comment"}
  let(:new_amount) {5646}
  let(:new_designation) {"new designation"}
  let(:new_fee) {54}
  let(:new_check_number) {"new check number"}

  let(:initial_time) {Time.now}


  let(:payment2_date) {initial_date + 10.days}

  before(:each) {
    initial_time
    payment
  }
  describe '.update_payment' do
    describe 'param validation' do
      it 'basic validation' do
        expect {UpdateDonation.update_payment(nil, nil)}.to raise_error {|error|
          expect(error).to be_a ParamValidation::ValidationError
          expect_validation_errors(error.data, [{key: :id, name: :required},
                                                {key: :id, name: :is_reference},
                                                {key: :data, name: :required},
                                                {key: :data, name: :is_hash}])
        }
      end

      it 'validates whether payment is valid' do
        expect{ UpdateDonation.update_payment(5555, {})}.to raise_error{|error|
          expect(error).to be_a ParamValidation::ValidationError
          expect_validation_errors(error.data, [{key: :id}])
          expect(error.message).to eq "5555 is does not correspond to a valid donation"
        }
      end

      describe 'data validation' do
        let(:initial_invalid_arguments) {{
            designation: 1,
            dedication: 1,
            comment: 1,
            campaign_id: nil,
            event_id: nil}}

        let(:expanded_invalid_arguments) {
          initial_invalid_arguments.merge({
                                              fee_total: 'fun',
                                              gross_amount: 'fun',
                                              check_number: Time.now,
                                              date: 'INVALID DATE'})
        }

        let(:initial_validation_errors) {[
            {key: :designation, name: :is_a},
            {key: :dedication, name: :is_a},
            {key: :comment, name: :is_a},
            {key: :campaign_id, name: :is_reference},
            {key: :campaign_id, name: :required},
            {key: :event_id, name: :is_reference},
            {key: :event_id, name: :required}
        ]}
        it 'for offsite donations' do
          offsite_payment
          expect {UpdateDonation.update_payment(donation.id, expanded_invalid_arguments)}.to(raise_error {|error|

            expect(error).to be_a ParamValidation::ValidationError
            expect_validation_errors(error.data, initial_validation_errors.concat([

                                                                                      {key: :fee_total, name: :is_integer},
                                                                                      {key: :gross_amount, name: :is_integer},
                                                                                      {key: :gross_amount, name: :min},
                                                                                      {key: :check_number, name: :is_a},
                                                                                      {key: :date, name: :can_be_date}
                                                                                  ]))
          })
        end

        it 'for online donation' do
          expect {UpdateDonation.update_payment(donation.id, expanded_invalid_arguments)}.to(raise_error {|error|

            expect(error).to be_a ParamValidation::ValidationError
            expect_validation_errors(error.data, initial_validation_errors)
          })
        end
      end

      it 'validate campaign_id' do
        expect {UpdateDonation.update_payment(donation.id, {campaign_id: 444, event_id: 444})}.to(raise_error {|error|
          expect(error).to be_a ParamValidation::ValidationError
          expect_validation_errors(error.data, [{key: :campaign_id}])
        })
      end

      it 'validate event_id' do
        expect {UpdateDonation.update_payment(donation.id, {event_id: 4444, campaign_id: campaign.id})}.to(raise_error {|error|
          expect(error).to be_a ParamValidation::ValidationError
          expect_validation_errors(error.data, [{key: :event_id}])
        })
      end

      it 'validates campaign belongs to payment org' do
        campaign_belongs {UpdateDonation.update_payment(donation.id, {campaign_id: other_campaign.id, event_id: event.id})}

      end

      it 'validates event belongs to payment org' do
        event_belongs {UpdateDonation.update_payment(donation.id, {event_id: other_event.id, campaign_id:campaign.id})}
      end


    end

    describe 'most of the values arent changed if not provided' do
      it 'online donation' do
        payment2
        result = verify_nothing_changed


        expect(result).to eq (donation.attributes.merge({payment: payment2.attributes}))
      end

      it 'offsite donation' do
        offsite_payment
        result = verify_nothing_changed

        p2_attributes = payment2.attributes
        payment2.reload
        expect(p2_attributes).to eq payment2.attributes

        o_attributes = offsite_payment.attributes
        offsite_payment.reload
        expect(o_attributes).to eq offsite_payment.attributes
        expect(result).to eq (donation.attributes.merge({payment: payment.attributes, offsite_payment: offsite_payment.attributes}))
      end

      def verify_nothing_changed
        result = UpdateDonation.update_payment(donation.id, {campaign_id: '', event_id: ''})


        p_attributes = payment.attributes
        payment.reload
        expect(p_attributes).to eq payment.attributes

        d_attributes = donation.attributes
        donation.reload
        expect(d_attributes).to eq donation.attributes

        result
      end
    end

    describe 'test everything changed' do
      let(:new_data) {{designation: new_designation,
                       dedication: new_dedication,
                       comment: new_comment,
                       campaign_id: campaign.id,
                       event_id: event.id,

                       gross_amount: new_amount,
                       fee_total: new_fee,
                       check_number: new_check_number,
                       date: new_date_input
      }}
      it 'online donation' do
        payment2
        Timecop.freeze(1.day) do
          result = UpdateDonation.update_payment(donation.id, new_data)


          expected_donation = donation.attributes.merge({designation: new_designation,
                                            dedication: new_dedication,
                                            comment: new_comment,
                                            campaign_id: campaign.id,
                                            event_id: event.id,
                                            updated_at: Time.now}).with_indifferent_access

          donation.reload
          expect(donation.attributes).to eq expected_donation

          expected_p1 = payment.attributes.merge({towards: new_designation, updated_at: Time.now}).with_indifferent_access
          payment.reload
          expect(payment.attributes).to eq expected_p1


          expected_p2 = payment2.attributes.merge({towards: new_designation, updated_at: Time.now}).with_indifferent_access

          payment2.reload
          expect(payment2.attributes).to eq expected_p2

          expected_offsite = offsite_payment.attributes
          offsite_payment.reload
          expect(offsite_payment.attributes).to eq expected_offsite

          expect(result).to eq create_expected_result(donation, payment2)
        end


      end

      it 'offline donation' do
        offsite_payment
        Timecop.freeze(1.day) do
          result = UpdateDonation.update_payment(donation.id, new_data)


          expected_donation = donation.attributes.merge({
              date: new_date,
              amount: new_amount,

              designation: new_designation,
              dedication: new_dedication,
              comment: new_comment,

              campaign_id: campaign.id,
              event_id: event.id,
              updated_at: Time.now,

          }).with_indifferent_access

          donation.reload
          expect(donation.attributes).to eq expected_donation

          expected_p1 = payment.attributes.merge({towards: new_designation, updated_at: Time.now, date: new_date, gross_amount: new_amount, fee_total: new_fee, net_amount: new_amount-new_fee}).with_indifferent_access
          payment.reload
          expect(payment.attributes).to eq expected_p1

          expect(Payment.count).to eq 1

          expected_offsite_payment= offsite_payment.attributes.merge({check_number:new_check_number, date: new_date.to_time_in_current_zone, gross_amount: new_amount, updated_at: Time.now}).with_indifferent_access

          offsite_payment.reload
          expect(offsite_payment.attributes).to eq expected_offsite_payment

          expect(result).to eq create_expected_result(donation, payment, offsite_payment)

        end
      end


      describe 'test blank but existent data will rewrite' do
        let(:blank_data) {{designation: '',
                         dedication: '',
                         comment: '',
                         campaign_id: '',
                         event_id: '',

                         gross_amount: new_amount,
                         fee_total: new_fee,
                         check_number: '',
                         date: new_date_input
        }}

        it 'online donation' do
          payment2
          Timecop.freeze(1.day) do
            UpdateDonation.update_payment(donation.id, new_data)
            result = UpdateDonation.update_payment(donation.id, blank_data)

            expected_donation = donation.attributes.merge({designation: '',
                                                          dedication: '',
                                                          comment: '',
                                                          campaign_id: nil,
                                                          event_id: nil,
                                                          updated_at: Time.now}).with_indifferent_access
            donation.reload

            expect(donation.attributes).to eq expected_donation

            expected_p1 = payment.attributes.merge({towards: '', updated_at: Time.now}).with_indifferent_access
            payment.reload
            expect(payment.attributes).to eq expected_p1


            expected_p2 = payment2.attributes.merge({towards: '', updated_at: Time.now}).with_indifferent_access

            payment2.reload
            expect(payment2.attributes).to eq expected_p2

            expected_offsite = offsite_payment.attributes
            offsite_payment.reload
            expect(offsite_payment.attributes).to eq expected_offsite

            expect(result).to eq create_expected_result(donation, payment2)

          end
        end

        it 'offline donation' do
          offsite_payment
          Timecop.freeze(1.day) do
            UpdateDonation.update_payment(donation.id, new_data)
            result = UpdateDonation.update_payment(donation.id, blank_data)

            expected_donation = donation.attributes.merge({
                                                              date: new_date.to_time_in_current_zone,
                                                              amount: new_amount,

                                                              designation: '',
                                                              dedication: '',
                                                              comment: '',

                                                              campaign_id: nil,
                                                              event_id: nil,
                                                              updated_at: Time.now,

                                                          }).with_indifferent_access

            donation.reload
            expect(donation.attributes).to eq expected_donation

            expected_p1 = payment.attributes.merge({towards: '', updated_at: Time.now, date: new_date.to_time_in_current_zone, gross_amount: new_amount, fee_total: new_fee, net_amount: new_amount-new_fee}).with_indifferent_access
            payment.reload
            expect(payment.attributes).to eq expected_p1

            expect(Payment.count).to eq 1

            expected_offsite_payment= offsite_payment.attributes.merge({check_number:'', date: new_date.to_time_in_current_zone, gross_amount: new_amount, updated_at: Time.now}).with_indifferent_access

            offsite_payment.reload
            expect(offsite_payment.attributes).to eq expected_offsite_payment

            expect(result).to eq create_expected_result(donation, payment, offsite_payment)
          end
        end
      end
    end
  end

  def event_belongs
    expect {yield}.to(raise_error {|error|
      expect(error).to be_a ParamValidation::ValidationError
      expect_validation_errors(error.data, [{key: :event_id}])
      expect(error.message).to include 'event does not belong to this nonprofit'
    })
  end

  def campaign_belongs
    expect {yield}.to(raise_error {|error|
      expect(error).to be_a ParamValidation::ValidationError
      expect_validation_errors(error.data, [{key: :campaign_id}])
      expect(error.message).to include 'campaign does not belong to this nonprofit'
    })
  end

  def create_expected_result(donation, payment, offsite_payment = nil)
    ret = donation.attributes
    ret[:payment] = payment.attributes
    if offsite_payment
      ret[:offsite_payment] = offsite_payment.attributes
    end

    ret
  end

end