# frozen_string_literal: true

#
# Copyright (C) 2013 - 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/>.

require_relative "../common"
require_relative "pages/course_people_modal"

describe "course people" do
  include_context "in-process server selenium tests"

  before do
    course_with_teacher_logged_in limit_privileges_to_course_section: false, active_all: true
    @account = @course.account # for custom roles
    @custom_student_role = custom_student_role("custom stu")
  end

  def add_user(email, type, section_name = nil)
    get "/courses/#{@course.id}/users"
    add_button = f("#addUsers")
    expect(add_button).to be_displayed
    add_button.click
    wait_for_ajaximations

    click_INSTUI_Select_option("#peoplesearch_select_role", type)
    click_INSTUI_Select_option("#peoplesearch_select_section", section_name) if section_name
    replace_content(f(".addpeople__peoplesearch textarea"), email)

    f("#addpeople_next").click
    expect(f(".addpeople__peoplereadylist tbody tr")).to include_text(email)

    f("#addpeople_next").click
  end

  describe "course users" do
    def select_from_auto_complete(text)
      select = f("[aria-label='Observee select']")
      select.send_keys(text)
      click_option(select, text)
    end

    def go_to_people_page
      get "/courses/#{@course.id}/users"
    end

    def kyle_menu_css(user, role = nil)
      if role
        role_name = if role.respond_to?(:name)
                      role.name
                    else
                      role
                    end
        "#user_#{user.id}.#{role_name} .admin-links"
      else
        "#user_#{user.id} .admin-links"
      end
    end

    def kyle_menu(user, role = nil)
      f(kyle_menu_css(user, role))
    end

    def open_kyle_menu(user, role = nil)
      cog = kyle_menu(user, role)
      f(".al-trigger", cog).click
      wait_for_ajaximations
      cog
    end

    def remove_user(user, role = nil)
      cog = open_kyle_menu(user, role)
      f('a[data-event="removeFromCourse"]', cog).click
      driver.switch_to.alert.accept
      wait_for_ajaximations
    end

    it "removes a user from the course" do
      username = "user@example.com"
      student_in_course(name: username, role: @custom_student_role)
      add_section("Section1")
      @enrollment.course_section = @course_section
      @enrollment.save!

      go_to_people_page
      expect(f(".roster")).to include_text(username)

      remove_user(@student)
      expect(f(".roster")).not_to include_text(username)
    end

    def add_user_to_second_section(role = nil, enrollment_state = "invited")
      role ||= student_role
      student_in_course(user: user_with_pseudonym, role:, enrollment_state:)
      section_name = "Another Section"
      add_section(section_name)
      # open tab
      go_to_people_page
      expect(f("#user_#{@student.id} .section")).not_to include_text(section_name)
      # open dialog
      use_edit_sections_dialog(@student) do
        # choose section
        CoursePeople.select_from_section_autocomplete(section_name)
      end
      # expect
      expect(f("#user_#{@student.id}")).to include_text(section_name)
      expect(ff("#user_#{@student.id} .section").length).to eq 2
      @student.reload
      @student.enrollments.each { |e| expect(e.role_id).to eq role.id }
      @student.enrollments.each { |e| expect(e.workflow_state).to eq enrollment_state }
    end

    it "adds a user without custom role to another section" do
      add_user_to_second_section
    end

    it "adds an active enrollment to another section if the user has already accepted their enrollment" do
      add_user_to_second_section(nil, "active")
    end

    it "adds a user to a second (active) section in a concluded course" do
      @course.start_at = 2.days.ago
      @course.conclude_at = 1.day.ago
      @course.restrict_enrollments_to_course_dates = true
      @course.save!

      student_in_course(user: user_with_pseudonym, role: student_role)
      section_name = "Another Section"
      add_section(section_name)

      @course_section.end_at = 1.day.from_now
      @course_section.restrict_enrollments_to_section_dates = true
      @course_section.save!

      # open tab
      go_to_people_page
      expect(f("#user_#{@student.id} .section")).not_to include_text(section_name)
      # open dialog
      use_edit_sections_dialog(@student) do
        # choose section
        CoursePeople.select_from_section_autocomplete(section_name)
      end
      # expect
      expect(f("#user_#{@student.id}")).to include_text(section_name)
      expect(ff("#user_#{@student.id} .section").length).to eq 2
      @student.reload
      expect(@student.enrollments.where(course_section: @course_section)).to be_exists
    end

    it "views the users enrollment details" do
      username = "user@example.com"
      # add_section 'foo'
      student_in_course(name: username, active_all: true)

      go_to_people_page
      # open dialog
      open_kyle_menu(@student)
      # when
      links = ff(".admin-links li a")
      link = links.detect { |l| l.text.include?("User Details") }
      href = link["href"]
      link.click
      wait_for_ajaximations
      wait_for_ajax_requests
      # expect
      expect(driver.current_url).to include(href)
    end

    it "is able to deactivate and reactivate users" do
      username = "user@example.com"
      student_in_course(name: username, active_all: true)

      go_to_people_page
      cog = open_kyle_menu(@student)
      link = f('a[data-event="deactivateUser"]', cog)
      expect(link).to include_text("Deactivate User")
      link.click
      driver.switch_to.alert.accept
      wait_for_ajaximations

      expect(f("#user_#{@student.id} span.label")).to include_text("inactive")
      @enrollment.reload
      expect(@enrollment.workflow_state).to eq "inactive"

      cog = open_kyle_menu(@student)
      link = f('a[data-event="reactivateUser"]', cog)
      expect(link).to include_text("Re-activate User")
      link.click
      wait_for_ajaximations
      expect(f("#content")).not_to contain_css("#user_#{@student.id} span.label")
      @enrollment.reload
      expect(@enrollment.workflow_state).to eq "active"
    end

    def use_link_dialog(observer, role = nil)
      cog = open_kyle_menu(observer, role)
      f('a[data-event="linkToStudents"]', cog).click
      wait_for_ajaximations
      yield
      f("[aria-label='Update']").click
      wait_for_ajaximations
    end

    def use_edit_sections_dialog(user, role = nil)
      cog = open_kyle_menu(user, role)
      f('a[data-event="editSections"]', cog).click
      wait_for_ajaximations
      yield
      f(".ui-dialog-buttonpane .btn-primary").click
      wait_for_ajaximations
    end

    it "deals with observers linked to multiple students" do
      students = []
      obs = user_with_pseudonym(name: "The Observer")
      2.times do |i|
        student_in_course(name: "Student #{i}")
        students << @student
        e = @course.observer_enrollments.create!(user: obs, workflow_state: "active")
        e.associated_user_id = @student.id
        e.save!
      end
      student_in_course(name: "Student 3")
      students << @student

      go_to_people_page

      observer_row = ff("#user_#{obs.id}").map(&:text).join(",")
      expect(observer_row).to include students[0].name
      expect(observer_row).to include students[1].name
      # remove an observer
      use_link_dialog(obs) do
        f("[aria-label='Observee select']").send_keys(:backspace)
      end
      # expect
      expect(obs.reload.not_ended_enrollments.count).to eq 1
      # add an observer
      use_link_dialog(obs) do
        select_from_auto_complete(students[2].name)
      end
      # expect
      expect(obs.reload.not_ended_enrollments.count).to eq 2
      expect(obs.reload.not_ended_enrollments.map(&:associated_user_id).sort).to include(students[2].id)
    end

    it "handles deleted observee enrollments" do
      custom_observer_role = custom_observer_role("obob")

      obs = user_model(name: "The Observer")
      student_in_course(name: "Student 1", active_all: true, role: @custom_student_role)
      @course.enroll_user(obs, "ObserverEnrollment", enrollment_state: "active", associated_user_id: @student.id, role: custom_observer_role)
      student_in_course(name: "Student 2", active_all: true, role: @custom_student_role)
      obs_enrollment =
        @course.enroll_user(
          obs,
          "ObserverEnrollment",
          enrollment_state: "active",
          associated_user_id: @student.id,
          allow_multiple_enrollments: true,
          role: custom_observer_role
        )

      # bye bye Student 2
      obs_enrollment.destroy

      go_to_people_page

      observer_row = ff("#user_#{obs.id}").map(&:text).join(",")
      expect(observer_row).to include "Student 1"
      expect(observer_row).not_to include "Student 2"

      # dialog loads too
      use_link_dialog(obs) do
        expect(f("[type=button][title$='Student 1']")).to be_truthy
        expect { f("[type=button][title$='Student 2']") }.to raise_error(Selenium::WebDriver::Error::NoSuchElementError)
      end
    end

    %w[ta designer].each do |et|
      it "does not let #{et}s remove admins from the course" do
        send :"custom_#{et}_role", "custom"
        send :"course_with_#{et}", course: @course, active_all: true, custom_role: "custom"
        user_session @user
        student_in_course user: user_with_pseudonym, course: @course, role: @custom_student_role

        go_to_people_page

        # should NOT see remove link for teacher
        expect(f("#content")).not_to contain_css(kyle_menu_css(@teacher))
        # should see remove link for student
        cog = open_kyle_menu @student
        expect(f('a[data-event="removeFromCourse"]', cog)).not_to be_nil
      end
    end

    it "does not show the student view student" do
      @fake_student = @course.student_view_student
      go_to_people_page
      expect(f("#content")).not_to contain_css(".student_enrollments #user_#{@fake_student.id}")
    end

    context "multiple enrollments" do
      it "links an observer enrollment when other enrollment types exist" do
        course_with_student course: @course, active_all: true, name: "teh student"
        course_with_ta user: user_with_pseudonym, course: @course, active_all: true
        course_with_observer course: @course, active_all: true, user: @ta

        go_to_people_page
        use_link_dialog(@observer, "ObserverEnrollment") do
          select_from_auto_complete(@student.name)
        end

        expect(@observer.enrollments.where(associated_user_id: @student)).to be_exists
        expect(f("#user_#{@observer.id}.ObserverEnrollment").text).to include("Observing: #{@student.name}")
      end
    end

    context "custom roles" do
      it "creates new observer enrollments as custom type when adding observees" do
        role = custom_observer_role("custom observer")
        student_in_course course: @course
        course_with_observer(course: @course, role:)

        go_to_people_page

        use_link_dialog(@observer) do
          select_from_auto_complete(@student.name)
        end

        @observer.reload
        @observer.enrollments.each { |e| expect(e.role_id).to eq role.id }
      end

      it "creates new enrollments as custom type when adding sections" do
        add_user_to_second_section(@custom_student_role)
      end

      def select_new_role_type(type)
        get "/courses/#{@course.id}/users"
        add_button = f("#addUsers")
        keep_trying_until { expect(add_button).to be_displayed }
        add_button.click
        click_INSTUI_Select_option("#role_id", type)
      end

      %w[student teacher ta designer observer].each do |base_type|
        it "allows adding custom #{base_type} enrollments" do
          user = user_with_pseudonym(active_all: true, username: "#{base_type}@example.com", name: "#{base_type}@example.com")
          send :"custom_#{base_type}_role", "custom"
          add_user(user.name, "custom")
          expect(f("#user_#{user.id} .admin-links")).not_to be_nil
        end
      end
    end

    it "does not remove a base enrollment when adding a custom enrollment of the same base type" do
      @role = custom_teacher_role "Mentor"
      add_user(@teacher.name, "Mentor")
      teacher_row = f("#user_#{@teacher.id}")
      expect(teacher_row).to have_class("TeacherEnrollment")
      expect(teacher_row).to_not have_class("Mentor")
    end
  end
end
