# frozen_string_literal: true

#
# Copyright (C) 2011 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

describe "MessageableUser" do
  describe ".build_select" do
    it "ignores common_course_column without common_role_column" do
      expect(MessageableUser.build_select(common_course_column: "ignored_column"))
        .to match(/NULL::text AS common_courses/)
    end

    it "requires common_course_column with common_role_column" do
      expect { MessageableUser.build_select(common_role_column: "role_column") }
        .to raise_error(ArgumentError)
    end

    it "combines common_course_column and common_role_column in common_courses" do
      course_with_student(active_all: true)
      messageable_user = MessageableUser
                         .select(MessageableUser.build_select(
                                   common_course_column: "'course_column'",
                                   common_role_column: "'role_column'"
                                 ))
                         .where(id: @student)
                         .group(MessageableUser.connection.group_by(*MessageableUser::COLUMNS))
                         .first
      expect(messageable_user.read_attribute(:common_courses))
        .to eq "course_column:role_column"
    end

    it "combines multiple (course,role) pairs in common_courses" do
      course_with_ta(active_all: true)
      multiple_student_enrollment(@ta, @course.course_sections.create!)
      messageable_user = MessageableUser
                         .select(MessageableUser.build_select(
                                   common_course_column: "'course'",
                                   common_role_column: "enrollments.type"
                                 ))
                         .joins("INNER JOIN #{Enrollment.quoted_table_name} ON enrollments.user_id=users.id")
                         .where(id: @ta.id)
                         .group(MessageableUser.connection.group_by(*MessageableUser::COLUMNS))
                         .first
      expect(messageable_user.read_attribute(:common_courses).split(",").sort)
        .to eq ["course:StudentEnrollment", "course:TaEnrollment"]
    end

    it "combines multiple common_group_column values in common_groups" do
      group1 = group_with_user(active_all: true).group
      group2 = group_with_user(user: @user, active_all: true).group
      messageable_user = MessageableUser
                         .select(MessageableUser.build_select(common_group_column: "group_memberships.group_id"))
                         .joins("INNER JOIN #{GroupMembership.quoted_table_name} ON group_memberships.user_id=users.id")
                         .where(id: @user)
                         .group(MessageableUser.connection.group_by(*MessageableUser::COLUMNS))
                         .first
      expect(messageable_user.read_attribute(:common_groups).split(",").map(&:to_i).sort)
        .to eq [group1.id, group2.id].sort
    end
  end

  describe ".prepped" do
    def group_scope(scope)
      scope.group_values.join(", ")
    end

    it "groups by id" do
      expect(group_scope(MessageableUser.prepped))
        .to match(MessageableUser::COLUMNS.first)
    end

    it "includes column-based common_course_column in group by" do
      expect(group_scope(MessageableUser.prepped(common_course_column: "course_column")))
        .to match("course_column")
    end

    it "includes column-based common_group_column in group by" do
      expect(group_scope(MessageableUser.prepped(common_group_column: "group_column")))
        .to match("group_column")
    end

    it "does not include literal common_course_column value in group by" do
      expect(group_scope(MessageableUser.prepped(common_course_column: 5)))
        .not_to match("5")
    end

    it "does not include literal common_group_column value in group by" do
      expect(group_scope(MessageableUser.prepped(common_group_column: 5)))
        .not_to match("5")
    end

    it "orders by sortable_name before id" do
      user1 = user_factory(active_all: true, name: "Yellow Bob")
      user2 = user_factory(active_all: true, name: "Zebra Alice")
      expect(MessageableUser.prepped.where(id: [user1, user2]).first.id).to eq user2.id
    end

    it "ignores case when ordering by sortable_name" do
      user1 = user_factory(active_all: true, name: "bob")
      user2 = user_factory(active_all: true, name: "ALICE")
      expect(MessageableUser.prepped.where(id: [user1, user2]).first.id).to eq user2.id
    end

    it "orders by id as tiebreaker" do
      user1 = user_factory(active_all: true, name: "Alice")
      user2 = user_factory(active_all: true, name: "Alice")
      expect(MessageableUser.prepped.where(id: [user1, user2]).first.id).to eq user1.id
    end

    it "excludes creation_pending students with strict_checks true" do
      user_factory(user_state: "creation_pending")
      expect(MessageableUser.prepped(strict_checks: true).where(id: @user).length).to eq 0
    end

    it "includes creation_pending students with strict_checks false" do
      user_factory(user_state: "creation_pending")
      expect(MessageableUser.prepped(strict_checks: false).where(id: @user).length).to eq 1
    end

    it "excludes deleted students with include_deleted true but strict_checks true" do
      user_factory(user_state: "deleted")
      expect(MessageableUser.prepped(strict_checks: true, include_deleted: true).where(id: @user).length).to eq 0
    end

    it "excludes deleted students with with strict_checks false but include_deleted false" do
      user_factory(user_state: "deleted")
      expect(MessageableUser.prepped(strict_checks: false, include_deleted: false).where(id: @user).length).to eq 0
    end

    it "includes deleted students with strict_checks false and include_deleted true" do
      user_factory(user_state: "deleted")
      expect(MessageableUser.prepped(strict_checks: false, include_deleted: true).where(id: @user).length).to eq 1
    end

    it "defaults strict_checks to true" do
      user_factory(user_state: "creation_pending")
      expect(MessageableUser.prepped.where(id: @user).length).to eq 0
    end

    it "defaults include_delete to false" do
      user_factory(user_state: "deleted")
      expect(MessageableUser.prepped(strict_checks: false).where(id: @user).length).to eq 0
    end
  end

  describe "#common_courses" do
    before do
      user_factory(active_all: true)
    end

    it "is empty with no common_courses selected" do
      expect(MessageableUser.prepped.where(id: @user).first.common_courses)
        .to eq({})
    end

    it "populates from non-null common_courses" do
      user = MessageableUser.prepped(common_course_column: 1, common_role_column: "'StudentEnrollment'").where(id: @user).first
      expect(user.common_courses).to eq({ 1 => ["StudentEnrollment"] })
    end

    describe "sharding" do
      specs_require_sharding

      it "translates keys to the current shard" do
        user = MessageableUser.prepped(common_course_column: Shard.relative_id_for(1, @shard2, Shard.current), common_role_column: "'StudentEnrollment'").where(id: @user).first
        [Shard.default, @shard1, @shard2].each do |shard|
          shard.activate do
            expect(user.common_courses).to eq({ Shard.relative_id_for(1, @shard2, Shard.current) => ["StudentEnrollment"] })
          end
        end
      end

      it "does not translate a 0 key" do
        user = MessageableUser.prepped(common_course_column: 0, common_role_column: "'StudentEnrollment'").where(id: @user).first
        [Shard.default, @shard1, @shard2].each do |shard|
          shard.activate do
            expect(user.common_courses).to eq({ 0 => ["StudentEnrollment"] })
          end
        end
      end
    end
  end

  describe "#common_groups" do
    before do
      user_factory(active_all: true)
    end

    it "is empty with no common_groups selected" do
      expect(MessageableUser.prepped.where(id: @user).first.common_groups)
        .to eq({})
    end

    it "populates from non-null common_groups with 'Member' roles" do
      user = MessageableUser.prepped(common_group_column: 1).where(id: @user).first
      expect(user.common_groups).to eq({ 1 => ["Member"] })
    end

    describe "sharding" do
      specs_require_sharding

      it "translates keys to the current shard" do
        user = MessageableUser.prepped(common_group_column: Shard.relative_id_for(1, @shard2, Shard.current)).where(id: @user).first
        [Shard.default, @shard1, @shard2].each do |shard|
          shard.activate do
            expect(user.common_groups).to eq({ Shard.relative_id_for(1, @shard2, Shard.current) => ["Member"] })
          end
        end
      end
    end
  end

  describe "#include_common_contexts_from" do
    before do
      user_factory(active_all: true)
    end

    it "merges disparate ids" do
      # e.g. two copies of the user from different shards with course
      # visibility on each
      user1 = MessageableUser.prepped(common_course_column: 1, common_role_column: "'StudentEnrollment'").where(id: @user).first
      user2 = MessageableUser.prepped(common_course_column: 2, common_role_column: "'StudentEnrollment'").where(id: @user).first
      user1.include_common_contexts_from(user2)
      expect(user1.common_courses[1]).to include("StudentEnrollment")
      expect(user1.common_courses[2]).to include("StudentEnrollment")
    end

    it "stacks coinciding ids" do
      # e.g. two copies of the user from different shards with admin visibility
      # on each
      user1 = MessageableUser.prepped(common_course_column: 0, common_role_column: "'StudentEnrollment'").where(id: @user).first
      user2 = MessageableUser.prepped(common_course_column: 0, common_role_column: "'TeacherEnrollment'").where(id: @user).first
      user1.include_common_contexts_from(user2)
      expect(user1.common_courses[0]).to include("StudentEnrollment")
      expect(user1.common_courses[0]).to include("TeacherEnrollment")
    end
  end
end
