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


describe InsertCustomFieldJoins do
  describe '.find_or_create' do
    let(:nonprofit) { force_create(:nonprofit)}
    let(:other_nonprofit) { force_create(:nonprofit)}
    let(:supporter) {force_create(:supporter, :nonprofit => nonprofit)}
    let(:other_supporter){force_create(:supporter, :nonprofit => other_nonprofit)}

    let(:initial_custom_field_master){ force_create(:custom_field_master, :nonprofit => nonprofit, :name => "CFM Name")}

    describe 'param validation' do
      it 'basic validation' do
        expect {InsertCustomFieldJoins.find_or_create(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 => :supporter_ids, :name => :required},
              {:key => :supporter_ids, :name => :is_array},
              {:key => :supporter_ids, :name => :min_length},
              {:key => :field_data, :name => :required},
              {:key => :field_data, :name => :is_array},
              {:key => :field_data, :name => :min_length}
          ])

        })
      end

      it 'validate nonprofit existence' do
        expect {InsertCustomFieldJoins.find_or_create(5, [555], [[1, 1]])}.to(raise_error {|error|
          expect(error).to be_a ParamValidation::ValidationError
          expect_validation_errors(error.data, [
              {:key => :np_id}
          ])

          expect(error.message).to eq "5 is not a valid non-profit"
        })
      end

      it 'validate supporter in nonprofit' do
        expect {InsertCustomFieldJoins.find_or_create(nonprofit.id, [other_supporter.id], [[1, 1]])}.to(raise_error {|error|
          expect(error).to be_a ParamValidation::ValidationError
          expect_validation_errors(error.data, [
              {:key => :supporter_ids}
          ])

          expect(error.message).to eq "#{other_supporter.id} is not a valid supporter for nonprofit #{nonprofit.id}"
        })
      end
    end

    it 'run insert' do

      new_cf_name = 'new cf name'
      new_cf_value = 'value'
      old_cf_value = 'old_cf_value'
      expect(InsertCustomFieldJoins).to receive(:in_bulk) do |np_id, supporters_id, field_data|
        expect(np_id).to eq nonprofit.id
        expect(supporters_id).to eq [supporter.id]
        expect(field_data.length).to eq 2
        expect(field_data).to include( { :custom_field_master_id => initial_custom_field_master.id, :value => old_cf_value})
        expect(field_data).to include( { :custom_field_master_id => CustomFieldMaster.where(:name => new_cf_name).first.id, :value => new_cf_value})
      end
      result = InsertCustomFieldJoins.find_or_create(nonprofit.id, [supporter.id], [
          [
              initial_custom_field_master.name,
              old_cf_value
          ],
          [
              new_cf_name,
              new_cf_value
          ]])
      expect(CustomFieldMaster.count).to eq 2
    end

  end


  describe '.in_bulk' do
    context 'parameter validation' do
      it 'should validate parameters' do

        response = InsertCustomFieldJoins::in_bulk(nil, nil, nil)
        errors = response[:json][:errors]
        expect(errors.length).to eq(6)
        expect(response[:status]).to eq (:unprocessable_entity)
        expect_validation_errors(errors, [
            {key: :np_id, name: :required},
            {key: :np_id, name: :is_integer},
            {key: :supporter_ids, name: :required},
            {key: :supporter_ids, name: :is_array},
            {key: :field_data, name: :is_array},
            {key: :field_data, name: :required}
        ])

      end
      context 'requiring db' do

        before(:each) {
            @nonprofit = force_create(:nonprofit)
            @bad_nonprofit = force_create(:nonprofit, :id => 50)
        }
        it 'nonprofit must be valid' do
          response = InsertCustomFieldJoins::in_bulk(@nonprofit.id+1, [], [])
          expect(response[:status]).to eq (:unprocessable_entity)
          expect(response[:json][:error]).to include("Nonprofit #{@nonprofit.id+1} is not valid")
        end

        it 'supporters if empty should do nothing' do
          response = InsertCustomFieldJoins::in_bulk(@nonprofit.id, [], [])
          expect(response).to eq(successful_json(0, 0))
        end

        it 'supporters if empty should do nothing' do
          response = InsertCustomFieldJoins::in_bulk(@nonprofit.id, [50], [])
          expect(response).to eq(successful_json(0, 0))
        end

      end


    end

    context 'main testing' do
      before(:each) {
        @nonprofit = force_create(:nonprofit)

        @random_supporter = create(:supporter)

        @other_nonprofit = force_create(:nonprofit)
        @delete_cfm= [20, 40, 60]
        @add_cfm = [25, 35]


        @supporters = {
            :np_supporter_with_add => {
                :cfm_ids => [65, 75, 85]
            },
            :np_supporter_with_cfms_to_delete => {
                :cfm_ids => [40, 75, 85]
            },

            :np_supporter_with_no_changes => {
                :cfm_ids => @add_cfm
            },
            :np_supporter_with_some_of_both => {

                :cfm_ids => [20, 35]
            },
            :supporter_from_other_np => {
                :cfm_ids => [100, 150, 200],
                :other_np => true
            }

        }

        @supporters.each_key {|k|
          i = @supporters[k]
          nonprofit_for_supporter =i[:other_np] ? @other_nonprofit : @nonprofit
          i[:entity] = create(:supporter, :nonprofit => nonprofit_for_supporter)
          i[:cfm_ids].each {|j|

            cfm = CustomFieldMaster.exists?(id: j) ? CustomFieldMaster.find(j) : create(:custom_field_master, id: j, nonprofit: nonprofit_for_supporter, name: "CFM #{j}")

            create(:custom_field_join, :value_from_id, :supporter_id => i[:entity].id, :custom_field_master => cfm)
          }
        }
      }

      it 'invalid nonprofit-supporter combo returns okay' do
        results = InsertCustomFieldJoins::in_bulk(@nonprofit.id, [@supporters[:supporter_from_other_np][:entity].id], [])
        expect(results).to eq(successful_json(0, 0))
      end


      it 'strips cfms which dont belong to nonprofit' do
        results = InsertCustomFieldJoins::in_bulk(@nonprofit.id, [@supporters[:np_supporter_with_add][:entity].id],
                                                  create_cfm_data([100], [150]))
        expect(results).to eq(successful_json(0, 0))

        expect(CustomFieldJoin.where('supporter_id = ? and custom_field_master_id = ?', @supporters[:np_supporter_with_add][:entity].id, 100).count).to eq 0

      end

      it 'delete' do

        expect(CustomFieldJoin.count).to eq 13
        @supporters[:np_supporter_with_some_of_both][:entity].id

        results = InsertCustomFieldJoins::in_bulk(@nonprofit.id,
                                                  [@supporters[:np_supporter_with_some_of_both][:entity].id, @supporters[:np_supporter_with_cfms_to_delete][:entity].id, @supporters[:np_supporter_with_add][:entity].id, @supporters[:supporter_from_other_np][:entity].id, @supporters[:np_supporter_with_no_changes][:entity].id],
                                                  create_cfm_data(@add_cfm, @delete_cfm))

        expect(CustomFieldJoin.where("supporter_id = ? ", @supporters[:np_supporter_with_some_of_both][:entity].id).count).to eq 2

        expect(CustomFieldJoin.where("supporter_id = ?", @supporters[:np_supporter_with_add][:entity].id).count).to eq 5

        expect(CustomFieldJoin.where("supporter_id = ?", @supporters[:np_supporter_with_cfms_to_delete][:entity].id).count).to eq 4

        expect(CustomFieldJoin.where("supporter_id = ?", @supporters[:supporter_from_other_np][:entity].id).count).to eq 3

        expect(CustomFieldJoin.where("supporter_id = ?", @supporters[:np_supporter_with_no_changes][:entity].id).count).to eq 2

        expect(CustomFieldJoin.count).to eq 16


      end

      it 'id, updated_at, created_at changes are stripped' do

        invalid_id = 10000000
        Timecop.freeze(2020, 9, 1, 12, 0, 0) {
          results = InsertCustomFieldJoins::in_bulk(@nonprofit.id,
                                                    [@supporters[:np_supporter_with_add][:entity].id],
                                                    [{custom_field_master_id: 25, value: "CFM value 25", id: invalid_id, created_at: Time.now.ago(3000), updated_at: Time.now.ago(2999)}])
          expected = {custom_field_master_id: 25, value: "CFM value 25", created_at: Time.now, updated_at: Time.now, supporter_id: @supporters[:np_supporter_with_add][:entity].id}.with_indifferent_access

          expect(results).to eq(successful_json(1, 0))

          result_tag = @supporters[:np_supporter_with_add][:entity].custom_field_joins.where('custom_field_master_id = ?', 25).first

          expect(result_tag.attributes.with_indifferent_access.reject {|k, _| k == 'id'}).to eq(expected)

          expect(result_tag.attributes[:id]).to_not eq invalid_id
        }
      end

      it 'add_to_one' do

        expect(CustomFieldJoin.count).to eq 13

        np_supporter_with_add_cfms = @supporters[:np_supporter_with_add][:entity].custom_field_joins.to_a
        np_supporter_with_some_of_both_cfms = @supporters[:np_supporter_with_some_of_both][:entity].custom_field_joins.to_a
        np_supporter_with_no_changes_cfms = @supporters[:np_supporter_with_no_changes][:entity].custom_field_joins.to_a

        Timecop.travel(20) {


          results = InsertCustomFieldJoins::in_bulk(@nonprofit.id,
                                                    [
                                                        @supporters[:np_supporter_with_add][:entity].id, #add 2
                                                        @supporters[:np_supporter_with_no_changes][:entity], # update 2
                                                        @supporters[:np_supporter_with_some_of_both][:entity].id], #add 2, delete 1
                                                    create_cfm_data(@add_cfm, @delete_cfm))

          expect(results).to eq(successful_json(6, 1))


          expect(@supporters[:np_supporter_with_no_changes][:entity].custom_field_joins).to match_array(np_supporter_with_no_changes_cfms)

          expect(CustomFieldJoin.where("supporter_id = ? ", @supporters[:np_supporter_with_add][:entity].id).count).to eq 5

          original_db_pairs = get_original_and_db(np_supporter_with_add_cfms, CustomFieldJoin.where("supporter_id = ? and custom_field_master_id in (?)",
                                                                                                    @supporters[:np_supporter_with_add][:entity].id,
                                                                                                    @supporters[:np_supporter_with_add][:cfm_ids]).pluck(:id))

          original_db_pairs.each {|orig, db|
            expect(db.attributes.length).to eq(orig.attributes.length)
            expect(db.attributes).to eq(orig.attributes)
          }

          expect(CustomFieldJoin.where("supporter_id = ?", @supporters[:np_supporter_with_some_of_both][:entity].id).count).to eq 2


          original_db_pairs = get_original_and_db(np_supporter_with_some_of_both_cfms, CustomFieldJoin.where("supporter_id = ? and custom_field_master_id in (?)",
                                                                                                             @supporters[:np_supporter_with_some_of_both][:entity].id,
                                                                                                             [35]).pluck(:id))
          skip_attribs = ['updated_at', 'value']
          original_db_pairs.each {|orig, db|
            expect(db.attributes.length).to eq(orig.attributes.length)
            expect(db.attributes.select {|key, value| !skip_attribs.include?(key)}).to eq(orig.attributes.select {|key, value| !skip_attribs.include?(key)})
            expect(db.attributes['updated_at']).to be > orig.attributes['updated_at']
            expect(db.attributes['value']).to eq "CFM value 35"


          }

          expect(CustomFieldJoin.count).to eq 15

        }


      end

    end
  end

  def successful_json(inserted, deleted)
    {:json => {:inserted_count => inserted, :removed_count => deleted}, :status => :ok}
  end

  def create_cfm_data(cfm_to_add =[], cfm_to_delete=[])
    use_nil = true
    cfm_to_add.map {|cfm| {custom_field_master_id: cfm, value: "CFM value #{cfm}"}} + cfm_to_delete.map {|cfm|
      value = use_nil ? nil : ''
      use_nil = !use_nil

      {custom_field_master_id: cfm, value: value}}
  end

  def get_original_and_db(original_items, ids_to_verify)
    ids_to_verify.map {|i|
      original_item = original_items.find {|oi| oi[:id] == i}
      db_item = CustomFieldJoin.find(i)
      [original_item, db_item]
    }
  end
end