# frozen_string_literal: true

#  Copyright (c) 2012-2013, Jungwacht Blauring Schweiz. This file is part of
#  hitobito and licensed under the Affero General Public License version 3
#  or later. See the COPYING file at the top-level directory or at
#  https://github.com/hitobito/hitobito.

require "spec_helper"

describe Event::ParticipantAssigner do
  let(:course) do
    course = Fabricate(:course, groups: [groups(:top_layer)], kind: event_kinds(:slk))
    course.questions << Fabricate(:event_question, event: course)
    course.questions << Fabricate(:event_question, event: course)
    course
  end

  let(:participation) do
    p = Fabricate(:event_participation, event: course, application: Fabricate(:event_application))
    Fabricate(course.participant_types.first.name.to_sym, participation: p)
    p
  end

  let(:event) { course }

  let(:pending_jobs) { Delayed::Job.where(locked_at: nil, failed_at: nil).map(&:payload_object) }

  subject { Event::ParticipantAssigner.new(event, participation) }

  describe "#add_participant" do
    before do
      participation.active = false
      participation.init_answers
      participation.save!
    end

    it "sets given participation active" do
      subject.add_participant
      participation.reload
      expect(participation).to be_active
    end

    it "queues confirmation mail" do
      subject.add_participant
      expect(pending_jobs).to include instance_of(Event::ParticipationConfirmationJob)
    end

    context "for other event" do
      let(:event) do
        quest = course.questions.first
        other = Fabricate(:course, groups: [groups(:top_layer)])
        other.questions << Fabricate(:event_question, event: other)
        other.questions << Fabricate(:event_question, event: other, question: quest.question, choices: quest.choices,
          multiple_choices: quest.multiple_choices)
        other
      end

      it "updates participation" do
        subject.add_participant

        expect(participation.event_id).to eq(event.id)
      end

      it "queues confirmation mail" do
        subject.add_participant
        expect(pending_jobs).to include instance_of(Event::ParticipationConfirmationJob)
      end

      it "updates answers so that every question of the new course has an answer" do
        expect { subject.add_participant }.to change { Event::Answer.count }.by(1)

        expect(participation.answers.count).to eq(3)
        answered = participation.reload.answers.map { |answer| answer.question.id }
        event.questions.each do |question|
          expect(answered).to include(question.id)
        end
      end

      context "with non default question" do
        class Event::Question::NonDefaultQuestion < ::Event::Question; end

        let(:event) do
          quest = course.questions.first
          other = Fabricate(:course, groups: [groups(:top_layer)])
          other.questions << Event::Question::NonDefaultQuestion.create!(event: other,
            disclosure: :optional,
            question: participation.answers.first.question.question)
          other.questions << Fabricate(:event_question, event: other, question: quest.question, choices: quest.choices,
            multiple_choices: quest.multiple_choices)
          other
        end

        it "updates participation" do
          subject.add_participant

          expect(participation.event_id).to eq(event.id)
        end
      end

      context "on existing participation" do
        it "raises error" do
          Fabricate(:event_participation, event: event, participant: participation.person,
            application: Fabricate(:event_application))

          expect { subject.add_participant }.to raise_error(ActiveRecord::RecordNotUnique)
        end

        it "queues confirmation mail" do
          subject.add_participant
          expect(pending_jobs).to include instance_of(Event::ParticipationConfirmationJob)
        end
      end
    end
  end

  describe "#remove_participant" do
    before do
      participation.active = true
      participation.save!
    end

    it "removes role for given application" do
      subject.remove_participant
      participation.reload
      expect(participation).not_to be_active
    end

    it "does not remove participation" do
      subject.remove_participant
      expect(Event::Participation.where(id: participation.id).exists?).to be_truthy
    end

    it "queues confirmation mail" do
      subject.remove_participant
      expect(pending_jobs).to include instance_of(Event::ParticipationConfirmationJob)
    end

    it "works even when the course in priority_1 does not exist anymore" do
      participation.application.update_attribute("priority_1_id", "99999")
      subject.remove_participant
      participation.reload
      expect(participation).not_to be_active
      expect(Event::Participation.where(id: participation.id).exists?).to be_truthy
    end

    context "roundtrip" do
      let(:event) { Fabricate(:course, groups: [groups(:top_layer)]) }

      it "resets the event to priority_1" do
        participation.application.priority_1 = participation.event
        participation.application.save!
        subject.add_participant
        expect(participation.reload.event).to eq(event)
        subject.remove_participant
        expect(participation.reload.event).to eq(course)
      end
    end
  end

  describe "participation with different prios" do
    let(:event1) { events(:top_course) }
    let(:event2) {
      Event::Course.create!(name: "Event 2", group_ids: event1.group_ids,
        dates: event1.dates, kind: event_kinds(:slk))
    }
    let(:assigner1) { Event::ParticipantAssigner.new(event1, participation) }
    let(:assigner2) { Event::ParticipantAssigner.new(event2, participation) }

    let(:participation) do
      p = Fabricate(:event_participation, event: event1, active: false)
      p.create_application!(priority_1: event1, priority_2: event2)
      Fabricate(course.participant_types.first.name.to_sym, participation: p)
      p.save!
      p.reload
      p
    end

    describe "#createable?" do
      it "is true for both when no role has been created" do
        expect(assigner1).to be_createable
        expect(assigner2).to be_createable
      end

      it "is false for assigner2 when already assigned to event1" do
        assigner1.add_participant
        expect(assigner2).not_to be_createable
      end

      it "is false for assigner1 when already assigned to event2" do
        assigner2.add_participant
        expect(assigner1).not_to be_createable
      end

      context "waiting list duplicate" do
        before do
          participation.application.update!(waiting_list: true, priority_2: nil)

          p = Fabricate(:event_participation, event: event2, participant: participation.person, active: false)
          p.create_application!(priority_1: event2)
          Fabricate(course.participant_types.first.name.to_sym, participation: p)
          p.save!
          p.reload
          p
        end

        it "is false for assigner2 when person on waiting list already applied" do
          # regression test for: https://github.com/hitobito/hitobito/issues/162
          expect(assigner2).not_to be_createable
        end
      end
    end

    describe "event#applicant_count" do
      before do
        participation
        assert_applicant_count(1, 0)
      end

      it "changes when participant role is created and destroyed in priority_2 event" do
        assigner2.add_participant
        assert_applicant_count(0, 1)

        assigner2.remove_participant
        assert_applicant_count(1, 0)
      end

      it "does not change when participant role is created and destroyed in priority_1 event" do
        assigner1.add_participant
        assert_applicant_count(1, 0)

        assigner1.remove_participant
        assert_applicant_count(1, 0)
      end

      def assert_applicant_count(count1, count2)
        expect(event1.reload.applicant_count).to eq count1
        expect(event2.reload.applicant_count).to eq count2
      end
    end
  end
end
