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

describe ExportPayments do
  before(:each) do
    stub_const('CHUNKED_UPLOADER',TestChunkedUploader)
   
    CHUNKED_UPLOADER.clear
  end

  let(:email) { 'example@example.com'}
  let(:user) {force_create(:user, email: email)}
  let(:nonprofit) { force_create(:nonprofit) }
  let(:supporters) { [ force_create(:supporter, name: "supporter-0", nonprofit: nonprofit),
                  force_create(:supporter, name: "supporter-1", nonprofit: nonprofit)]}
  let(:payments)  {[force_create(:payment, gross_amount: 1000, fee_total: 99, net_amount: 901, supporter: supporters[0], nonprofit:nonprofit),
               force_create(:payment, gross_amount: 2000, fee_total: 22, net_amount: 1978, supporter: supporters[1], nonprofit:nonprofit)]}

  before(:each) {
      payments
  }
  context '.initiate_export' do
    context 'param verification' do
      it 'performs initial verification' do
        expect { ExportPayments.initiate_export(nil, nil, nil) }.to(raise_error do |error|
          expect(error).to be_a(ParamValidation::ValidationError)
          expect(error.data.length).to eq(6)
          expect_validation_errors(error.data, [{ key: 'npo_id', name: :required },
                                                { key: 'npo_id', name: :is_integer },
                                                { key: 'user_id', name: :required },
                                                { key: 'user_id', name: :is_integer },
                                                { key: 'params', name: :required },
                                                { key: 'params', name: :is_hash }])
        end)
      end

      it 'nonprofit doesnt exist' do
        fake_npo = 8_888_881
        expect { ExportPayments.initiate_export(fake_npo, {}, 8_888_883) }.to(raise_error do |error|
          expect(error).to be_a(ParamValidation::ValidationError)
          expect(error.message).to eq "Nonprofit #{fake_npo} doesn't exist!"
        end)
      end

      it 'user doesnt exist' do
        fake_user = 8_888_883
        expect { ExportPayments.initiate_export(nonprofit.id, {}, fake_user) }.to(raise_error do |error|
          expect(error).to be_a(ParamValidation::ValidationError)
          expect(error.message).to eq "User #{fake_user} doesn't exist!"
        end)
      end
    end

    it 'creates an export object and schedules job' do
      Timecop.freeze(2020, 4, 5) do
        stub_const("DelayedJobHelper", double('delayed'))
        params =  { param1: 'pp' }.with_indifferent_access

        expect(DelayedJobHelper).to receive(:enqueue_job).with(ExportPayments, :run_export, [nonprofit.id, params.to_json, user.id, 1])

        ExportPayments.initiate_export(nonprofit.id, params, user.id)
        export = Export.first
        expected_export = { id: 1,
                            user_id: user.id,
                            nonprofit_id: nonprofit.id,
                            status: 'queued',
                            export_type: 'ExportPayments',
                            parameters: params.to_json,
                            updated_at: Time.now,
                            created_at: Time.now,
                            url: nil,
                            ended: nil,
                            exception: nil }.with_indifferent_access
        expect(export.attributes).to eq(expected_export)
      end
    end
  end
  context '.run_export' do
    context 'param validation' do
      it 'rejects basic invalid data' do
        expect { ExportPayments.run_export(nil, nil, nil, nil) }.to(raise_error do |error|
          expect(error).to be_a(ParamValidation::ValidationError)
          expect_validation_errors(error, [{ key: 'npo_id', name: :required },
                                           { key: 'npo_id', name: :is_integer },
                                           { key: 'user_id', name: :required },
                                           { key: 'user_id', name: :is_integer },
                                           { key: 'params', name: :required },
                                           { key: 'params', name: :is_json },
                                           { key: 'export_id', name: :required },
                                           { key: 'export_id', name: :is_integer }])
        end)
      end

      it 'rejects json which isnt a hash' do
        expect { ExportPayments.run_export(1, [{ item: '' }, { item: '' }].to_json, 1, 1) }.to(raise_error do |error|
          expect(error).to be_a(ParamValidation::ValidationError)
          expect_validation_errors(error, [
                                     { key: :params, name: :is_hash }
                                   ])
        end)
      end

      it 'no export throw an exception' do
        expect { ExportPayments.run_export(0, { x: 1 }.to_json, 0, 11_111) }.to(raise_error do |error|
          expect(error).to be_a ParamValidation::ValidationError
          expect(error.data[:key]).to eq :export_id
          expect(error.message).to start_with('Export')
        end)
      end

      it 'no nonprofit' do
        Timecop.freeze(2020, 4, 5) do
          @export = force_create(:export, user: user)
          Timecop.freeze(2020, 4, 6) do
            expect { ExportPayments.run_export(0, { x: 1 }.to_json, user.id, @export.id) }.to(raise_error do |error|
              expect(error).to be_a ParamValidation::ValidationError
              expect(error.data[:key]).to eq :npo_id
              expect(error.message).to start_with('Nonprofit')

              @export.reload
              expect(@export.status).to eq 'failed'
              expect(@export.exception).to eq error.to_s
              expect(@export.ended).to eq Time.now
              expect(@export.updated_at).to eq Time.now

              #expect(user).to have_received_email(subject: "Your payment export has failed")
            end)
          end
        end
      end

      it 'no user' do
        Timecop.freeze(2020, 4, 5) do
          @export = force_create(:export, user: user)
          Timecop.freeze(2020, 4, 6) do
            expect { ExportPayments.run_export(nonprofit.id, { x: 1 }.to_json, 0, @export.id) }.to(raise_error do |error|
              expect(error).to be_a ParamValidation::ValidationError
              expect(error.data[:key]).to eq :user_id
              expect(error.message).to start_with('User')

              @export.reload
              expect(@export.status).to eq 'failed'
              expect(@export.exception).to eq error.to_s
              expect(@export.ended).to eq Time.now
              expect(@export.updated_at).to eq Time.now


            end)
          end
        end
      end
    end

    it 'handles exception in upload properly' do
      Timecop.freeze(2020, 4, 5) do
        @export = force_create(:export, user: user)
        CHUNKED_UPLOADER.raise_error
        Timecop.freeze(2020, 4, 6) do
          expect { ExportPayments.run_export(nonprofit.id, {}.to_json, user.id, @export.id) }.to(raise_error do |error|
            expect(error).to be_a StandardError
            expect(error.message).to eq TestChunkedUploader::TEST_ERROR_MESSAGE

            @export.reload
            expect(@export.status).to eq 'failed'
            expect(@export.exception).to eq error.to_s
            expect(@export.ended).to eq Time.now
            expect(@export.updated_at).to eq Time.now

            expect(user).to have_received_email(subject: "Your payment export has failed")
          end)
        end
      end
    end

    it 'uploads as expected' do
      Timecop.freeze(2020, 4, 5) do
        @export = create(:export, user: user, created_at: Time.now, updated_at: Time.now)
        Timecop.freeze(2020, 4, 6, 1, 2, 3) do
          ExportPayments.run_export(nonprofit.id, {}.to_json, user.id, @export.id)

          @export.reload

          expect(@export.url).to eq 'http://fake.url/tmp/csv-exports/payments-04-06-2020--01-02-03.csv'
          expect(@export.status).to eq 'completed'
          expect(@export.exception).to be_nil
          expect(@export.ended).to eq Time.now
          expect(@export.updated_at).to eq Time.now
          csv = CSV.parse(TestChunkedUploader.output)
          expect(csv.length).to eq (3)

          expect(csv[0]).to eq MockHelpers.payment_export_headers

          expect(TestChunkedUploader.options[:content_type]).to eq 'text/csv'
          expect(TestChunkedUploader.options[:content_disposition]).to eq 'attachment'
          expect(user).to have_received_email(subject: "Your payment export is available!")
        end
      end
    end
  end
end