require "spec_helper"

class FakesController < ApplicationController
  include CollaboratorProcessing
end

describe FakesController do
  let!(:fanny) { create(:user, first_name: "Fanny") }
  let!(:cookbook) { create(:cookbook, owner: fanny) }

  let(:user1) { create(:user) }
  let(:user2) { create(:user) }
  let(:user3) { create(:user) }

  let(:users) { [user1, user2, user3] }
  let(:user_ids) { "#{user1.id},#{user2.id},#{user3.id}" }

  before do
    allow(subject).to receive(:current_user) { create(:user) }
    sign_in fanny
  end

  context "adding users" do
    context "finding non-eligible user ids" do
      before do
        allow(Collaborator).to receive(:ineligible_collaborators_for).and_return(users)
      end

      it "finds non-eligible users" do
        expect(Collaborator).to receive(:ineligible_collaborators_for).with(cookbook)
        subject.add_users_as_collaborators(cookbook, user_ids)
      end

      it "maps the user ids" do
        expect(users).to receive(:map).and_return([user1.id, user2.id, user3.id])
        subject.add_users_as_collaborators(cookbook, user_ids)
      end

      it "converts the ids to strings" do
        id_array = [user1.id, user2.id, user3.id]

        allow(users).to receive(:map).and_return(id_array)
        expect(id_array).to receive(:map).and_return(["#{user1.id}", "#{user2.id}", "#{user3.id}"])
        subject.add_users_as_collaborators(cookbook, user_ids)
      end
    end

    context "finding users" do
      it "finds all eligible users" do
        expect(User).to receive(:where).with(id: ["#{user1.id}", "#{user2.id}", "#{user3.id}"]).and_return(users)
        subject.add_users_as_collaborators(cookbook, user_ids)
      end

      it "does not include non-eligible users" do
        allow(Collaborator).to receive(:ineligible_collaborators_for).and_return([user2])
        expect(User).to receive(:where).with(id: ["#{user1.id}", "#{user3.id}"]).and_return([user1, user2])
        subject.add_users_as_collaborators(cookbook, user_ids)
      end
    end

    context "for each user" do
      let(:user) { create(:user) }
      let(:user_ids) { "#{user.id}" }
      let(:new_collaborator) { build(:cookbook_collaborator, user: user, resourceable: cookbook) }

      it "creates a new collaborator" do
        expect(Collaborator).to receive(:new).with(user_id: user.id, resourceable: cookbook, group_id: nil).and_return(new_collaborator)
        subject.add_users_as_collaborators(cookbook, user_ids)
      end

      it "authorizes the collaborator" do
        expect(subject).to receive(:authorize!)
        subject.add_users_as_collaborators(cookbook, user_ids)
      end

      context "when the current user is the owner of the resource" do
        before do
          expect(subject.current_user).to eq(fanny)
          expect(cookbook.owner).to eq(fanny)
        end

        before do
          allow(Collaborator).to receive(:new).and_return(new_collaborator)
        end

        it "saves the new collaborator" do
          expect(new_collaborator).to receive(:save!)
          subject.add_users_as_collaborators(cookbook, user_ids)
        end

        it "queues a mailer" do
          collaborator_mailer = double("CollaboratorMailer", delay: "true")
          expect(CollaboratorMailer).to receive(:delay).and_return(collaborator_mailer)

          expect(collaborator_mailer).to receive(:added_email).with(new_collaborator)
          subject.add_users_as_collaborators(cookbook, user_ids)
        end
      end

      context "when the current user is not the owner of the resource" do
        let(:hank) { create(:user, first_name: "Hank") }
        before do
          sign_in hank
          expect(subject.current_user).to eq(hank)
          expect(cookbook.owner).to_not eq(hank)
        end

        it "returns an error" do
          expect do
            subject.add_users_as_collaborators(cookbook, user_ids)
          end.to raise_error(Pundit::NotAuthorizedError)
        end
      end
    end
  end

  context "adding collaborator groups" do
    let!(:group_member) { create(:group_member) }
    let(:group) { group_member.group }

    let!(:group_member2) { create(:group_member, group: group) }

    context "adding a single group" do
      before do
        expect(cookbook.owner).to eq(fanny)
        sign_in fanny

        allow(Group).to receive(:find).and_return(group)
        allow(group).to receive(:members).and_return(group.members)
      end

      it "finds the correct group" do
        expect(Group).to receive(:find).with(group.id.to_s).and_return(group)
        subject.add_group_members_as_collaborators(cookbook, "#{group.id}")
      end

      it "associates the group with the resource" do
        expect(group).to receive(:group_resources).at_least(:once).and_return([])
        new_group_resource = create(:group_resource, group: group)

        allow(GroupResource).to receive(:create!).and_return(new_group_resource)

        expect(group.group_resources).to receive(:<<).with(new_group_resource)

        subject.add_group_members_as_collaborators(cookbook, "#{group.id}")
      end

      it "finds the members for the group" do
        expect(group).to receive(:members).and_return(group.members)
        subject.add_group_members_as_collaborators(cookbook, "#{group.id}")
      end

      it "maps the user ids" do
        expect(group.members).to receive(:map).and_return(group.members.map(&:id))
        subject.add_group_members_as_collaborators(cookbook, "#{group.id}")
      end

      it "transforms the user ids into a string" do
        member_ids = group.members.map(&:id)
        allow(group.members).to receive(:map).and_return(member_ids)
        expect(member_ids).to receive(:map).and_return(member_ids.map(&:to_s))

        subject.add_group_members_as_collaborators(cookbook, "#{group.id}")
      end

      it "makes a collaborator for each group user" do
        user_ids = group.members.map(&:id).map(&:to_s)

        expect(subject).to receive(:add_users_as_collaborators).with(cookbook, user_ids, group.id.to_s)
        subject.add_group_members_as_collaborators(cookbook, "#{group.id}")
      end

      it "associates the collaborator with the group" do
        expect(cookbook.collaborators).to be_empty
        subject.add_group_members_as_collaborators(cookbook, "#{group.id}")
        cookbook.collaborators.each do |collaborator|
          expect(collaborator.group).to eq(group)
        end
      end
    end

    context "adding multiple groups" do
      let!(:group2_member) { create(:group_member) }
      let(:group2) { group2_member.group }
      let!(:group2_member2) { create(:group_member, group: group2) }

      it "finds all groups" do
        expect(Group).to receive(:find).at_least(:twice).and_return(group, group2)
        subject.add_group_members_as_collaborators(cookbook, "#{group.id}, #{group2.id}")
      end

      it "makes a new collaborator for each user in both groups" do
        expect(cookbook.collaborators).to be_empty

        subject.add_group_members_as_collaborators(cookbook, "#{group.id},#{group2.id}")

        collaborator_user_ids = cookbook.collaborators.map(&:user_id)
        expect(collaborator_user_ids).to include(group_member.user.id, group2_member.user.id, group2_member2.user.id)
      end
    end
  end

  context "removing collaborators" do
    let!(:hank) { create(:user, first_name: "Hank") }
    let!(:hanky) { create(:user, first_name: "Hanky") }

    let!(:collaborator) { create(:cookbook_collaborator, resourceable: cookbook, user: hank) }

    it "allows a resource owner to remove a collaborator" do
      sign_in fanny

      expect do
        subject.remove_collaborator(collaborator)
      end.to change { Collaborator.count }.by(-1)

      expect(response).to be_successful
    end

    it "allows a collaborator to remove themselves as a collaborator" do
      sign_in hank

      expect do
        subject.remove_collaborator(collaborator)
      end.to change { Collaborator.count }.by(-1)

      expect(response).to be_successful
    end

    it "does not allow a non-collaborator to remove a collaborator" do
      sign_in hanky

      expect do
        subject.remove_collaborator(collaborator)
      end.to raise_error(Pundit::NotAuthorizedError)
    end

    it "does not allow a collaborator to remove a collaborator other than themselves" do
      hanky_collaborator = create(:cookbook_collaborator, resourceable: cookbook, user: hanky)
      expect(cookbook.collaborators).to include(hanky_collaborator)

      sign_in hanky

      expect do
        subject.remove_collaborator(collaborator)
      end.to raise_error(Pundit::NotAuthorizedError)
    end
  end

  context "removing groups of collaborators" do
    let!(:group_member1) { create(:group_member) }
    let!(:group) { group_member1.group }
    let!(:group_member2) { create(:group_member, group: group) }

    let(:collaborator1) { create(:cookbook_collaborator, group: group, user: group_member1.user, resourceable: cookbook) }
    let(:collaborator2) { create(:cookbook_collaborator, group: group, user: group_member2.user, resourceable: cookbook) }

    let(:group_resource) { create(:group_resource, resourceable: cookbook, group: group) }

    before do
      expect(group.group_members).to include(group_member1, group_member2)
      expect(cookbook.group_resources).to include(group_resource)
      expect(cookbook.collaborators).to include(collaborator1, collaborator2)
    end

    it "removes all collaborators associated with the group" do
      group_collaborators = Collaborator.where(resourceable: cookbook, group: group)
      expect(subject).to receive(:remove_collaborator).with(collaborator1)
      expect(subject).to receive(:remove_collaborator).with(collaborator2)
      subject.remove_group_collaborators(group_collaborators)
    end
  end
end
