# 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/>.

describe Quizzes::QuizSerializer do
  def quiz_serializer(options = {})
    options.reverse_merge!({
                             controller:,
                             scope: @user,
                             session: @session
                           })
    Quizzes::QuizSerializer.new(@quiz, options)
  end
  let(:quiz) { @quiz }
  let(:context) { @context }
  let(:serializer) { @serializer }
  let(:host_name) { "example.com" }
  let(:json) { @json }
  let(:session) { @session }
  let(:controller) do
    ActiveModel::FakeController.new(accepts_jsonapi: true, stringify_json_ids: false)
  end

  before do
    @context = Account.default.courses.new
    @context.id = 1
    @quiz = Quizzes::Quiz.new title: "test quiz", description: "default quiz description"
    @quiz.id = 1
    @quiz.context = @context
    @user = User.new
    allow(@quiz).to receive_messages(locked_for?: false, check_right?: true)
    @session = {}
    allow(controller).to receive_messages(session:, context:)
    allow(@quiz).to receive(:grants_right?).at_least(:once).and_return true
    allow(@context).to receive(:grants_right?).at_least(:once).and_return true
    allow(@context).to receive(:grants_any_right?).at_least(:once).and_return true
    @serializer = quiz_serializer
    @json = @serializer.as_json[:quiz]
  end

  %i[
    title
    quiz_type
    hide_results
    time_limit
    shuffle_answers
    show_correct_answers
    scoring_policy
    allowed_attempts
    one_question_at_a_time
    question_count
    points_possible
    cant_go_back
    access_code
    ip_filter
    due_at
    lock_at
    unlock_at
    published
    show_correct_answers_at
    hide_correct_answers_at
    show_correct_answers_last_attempt
    has_access_code
    migration_id
  ].each do |attribute|
    it "serializes #{attribute}" do
      expect(json[attribute]).to eq quiz.send(attribute)
    end
  end

  it "serializes mobile_url" do
    expect(json[:mobile_url]).to eq "http://example.com/courses/1/quizzes/1?force_user=1&persist_headless=1"
  end

  it "serializes html_url" do
    expect(json[:html_url]).to eq "http://example.com/courses/1/quizzes/1"
  end

  it "serializes timer_autosubmit_disabled" do
    expect(json[:timer_autosubmit_disabled]).to eq quiz.send(:timer_autosubmit_disabled?)
  end

  describe "description" do
    it "serializes description with a formatter if given" do
      @serializer = quiz_serializer(
        serializer_options: {
          description_formatter: ->(_) { "description from formatter" }
        }
      )
      json = @serializer.as_json[:quiz]

      expect(json[:description]).to eq "description from formatter"
    end

    it "returns desctiption otherwise" do
      expect(json[:description]).to eq quiz.description
    end
  end

  describe "description for locked quiz" do
    it "returns an empty string for students" do
      serializer = quiz_serializer
      allow(serializer).to receive("quiz_locked_for_user?").and_return true
      allow(serializer).to receive("user_is_student?").and_return true
      json = serializer.as_json[:quiz]
      expect(json[:description]).to eq ""
    end

    it "returns description for non-students" do
      json = serializer.as_json[:quiz]
      allow(serializer).to receive("quiz_locked_for_user?").and_return true
      allow(serializer).to receive("user_is_student?").and_return false
      expect(json[:description]).to eq quiz.description
    end
  end

  it "serializes speed_grader_url" do
    # No assignment, so it should be nil
    expect(json[:speed_grader_url]).to be_nil
    assignment = Assignment.new
    assignment.id = 1
    assignment.context_id = @context.id
    allow(@quiz).to receive_messages(assignment:, grants_right?: false)

    # nil when quiz is unpublished
    allow(@quiz).to receive(:published?).and_return false
    expect(@serializer.as_json[:quiz][:speed_grader_url]).to be_nil

    # nil when context doesn't allow speedgrader
    allow(@quiz).to receive(:published?).and_return true
    expect(assignment).to receive(:can_view_speed_grader?).with(@user).and_return false
    expect(@serializer.as_json[:quiz][:speed_grader_url]).to be_nil

    expect(assignment).to receive(:can_view_speed_grader?).with(@user).and_return true
    json = @serializer.as_json[:quiz]
    expect(json[:speed_grader_url]).to eq(
      controller.send(:speed_grader_course_gradebook_url, @quiz.context, assignment_id: @quiz.assignment.id)
    )

    # Students shouldn't be able to see speed_grader_url
    allow(@context).to receive(:grants_right?).and_return false
    expect(@serializer.as_json[:quiz]).not_to have_key :speed_grader_url
  end

  it "doesn't include the section count unless the user can grade" do
    result = true
    allow(quiz.context).to receive(:grants_right?).with(@user, :manage_grades) { result }
    expect(serializer.as_json[:quiz]).to have_key :section_count

    result = false
    expect(serializer.as_json[:quiz]).not_to have_key :section_count
  end

  it "uses available_question_count for question_count" do
    allow(quiz).to receive(:available_question_count).and_return 5
    expect(serializer.as_json[:quiz][:question_count]).to eq 5
  end

  it "sends the message_students_url when user can grade" do
    result = true
    allow(quiz.context).to receive(:grants_right?) { result }
    expect(serializer.as_json[:quiz][:message_students_url]).to eq(
      controller.send(:api_v1_course_quiz_submission_users_message_url, quiz, quiz.context)
    )

    result = false
    expect(serializer.as_json[:quiz]).not_to have_key :message_students_url
  end

  describe "access code" do
    it "is included if the user can grade" do
      expect(quiz.context).to receive(:grants_right?).with(@user, :manage_grades)
                                                     .at_least(:once).and_return true
      expect(serializer.as_json[:quiz]).to have_key :access_code
    end

    it "is included if the user can manage" do
      expect(quiz.context).to receive(:grants_any_right?).with(@user, *RoleOverride::GRANULAR_MANAGE_ASSIGNMENT_PERMISSIONS)
                                                         .at_least(:once).and_return true
      expect(serializer.as_json[:quiz]).to have_key :access_code
    end

    it "is not included if the user can't grade or manage" do
      expect(quiz.context).to receive(:grants_right?).with(@user, :manage_grades)
                                                     .at_least(:once).and_return false
      expect(quiz.context).to receive(:grants_any_right?).with(@user, *RoleOverride::GRANULAR_MANAGE_ASSIGNMENT_PERMISSIONS)
                                                         .at_least(:once).and_return false
      expect(serializer.as_json[:quiz]).not_to have_key :access_code
    end
  end

  describe "id" do
    it "stringifys when stringify_json_ids? is true" do
      expect(controller).to receive(:accepts_jsonapi?).at_least(:once).and_return false
      expect(controller).to receive(:stringify_json_ids?).at_least(:once).and_return true
      expect(serializer.as_json[:quiz][:id]).to eq quiz.id.to_s
    end

    it "when stringify_json_ids? is false" do
      expect(controller).to receive(:accepts_jsonapi?).at_least(:once).and_return false
      expect(serializer.as_json[:quiz][:id]).to eq quiz.id
      expect(serializer.as_json[:quiz][:id].is_a?(Integer)).to be_truthy
    end
  end

  describe "lock_info" do
    it "includes lock_info when appropriate" do
      expect(quiz).to receive(:locked_for?)
        .with(@user, check_policies: true, context: @context)
        .and_return({ due_at: true })
      json = quiz_serializer.as_json[:quiz]
      expect(json).to have_key :lock_info
      expect(json).to have_key :lock_explanation
      expect(json[:locked_for_user]).to be true

      expect(quiz).to receive(:locked_for?)
        .with(@user, check_policies: true, context: @context)
        .and_return false
      json = quiz_serializer.as_json[:quiz]
      expect(json).not_to have_key :lock_info
      expect(json).not_to have_key :lock_explanation
      expect(json[:locked_for_user]).to be false
    end

    it "doesn't if skip_lock_tests is on" do
      json = quiz_serializer({
                               serializer_options: {
                                 skip_lock_tests: true
                               }
                             }).as_json[:quiz]
      expect(json).not_to have_key :lock_info
      expect(json).not_to have_key :lock_explanation
      expect(json).not_to have_key :locked_for_user
    end
  end

  describe "unpublishable" do
    it "is not present unless the user can manage the quiz's assignments" do
      manage_result = true
      allow(context).to receive(:grants_any_right?).with(@user, *RoleOverride::GRANULAR_MANAGE_ASSIGNMENT_PERMISSIONS) { manage_result }
      expect(serializer.filter(serializer.class._attributes)).to include :unpublishable

      manage_result = false
      expect(serializer.filter(serializer.class._attributes)).not_to include :unpublishable
    end
  end

  describe "preview_url" do
    it "is only present when the user can grade the quiz" do
      course_with_teacher(active_all: true)
      course_quiz(true)
      expect(quiz_serializer(scope: @teacher).as_json[:quiz][:preview_url])
        .to eq controller.send(:course_quiz_take_url, @quiz.context, @quiz, preview: "1")
      course_with_student(active_all: true, course: @course)
      expect(quiz_serializer(scope: @student).as_json[:quiz]).not_to have_key :preview_url
    end
  end

  describe "links" do
    describe "assignment_group" do
      context "controller doesn't accept jsonapi" do
        it "serialized the assignment_group as assignment_group_id" do
          expect(controller).to receive(:accepts_jsonapi?).at_least(:once).and_return false
          expect(serializer.as_json[:quiz]["assignment_group_id"]).to be_nil

          group = quiz.assignment_group = AssignmentGroup.new
          group.id = quiz.assignment_group_id = 1
          expect(serializer.as_json[:quiz][:assignment_group_id]).to eq 1
        end
      end
    end

    describe "quiz_reports" do
      it "sends the url" do
        allow(quiz).to receive_messages(context: Account.default.courses.create!)
        expect(serializer.as_json[:quiz]["links"]["quiz_reports"]).to eq(
          controller.send(:api_v1_course_quiz_reports_url, quiz.context.id, quiz.id)
        )
      end

      it "sends the url as quiz_reports_url" do
        expect(controller).to receive(:accepts_jsonapi?).at_least(:once).and_return false
        allow(quiz).to receive_messages(context: Account.default.courses.create!)
        expect(serializer.as_json[:quiz][:quiz_reports_url]).to eq(
          controller.send(:api_v1_course_quiz_reports_url, quiz.context.id, quiz.id)
        )
      end
    end

    describe "quiz_statistics" do
      it "sends the url" do
        allow(quiz).to receive_messages(context: Account.default.courses.create!)
        expect(serializer.as_json[:quiz]["links"]["quiz_statistics"]).to eq(
          controller.send(:api_v1_course_quiz_statistics_url, quiz.context.id, quiz.id)
        )
      end

      it "sends the url in non-JSONAPI too" do
        expect(controller).to receive(:accepts_jsonapi?).at_least(:once).and_return false
        allow(quiz).to receive_messages(context: Account.default.courses.create!)
        expect(serializer.as_json[:quiz][:quiz_statistics_url]).to eq(
          controller.send(:api_v1_course_quiz_statistics_url, quiz.context.id, quiz.id)
        )
      end
    end
  end

  describe "quiz_submission_html_url" do
    it "includes a url to the quiz_submission html only if JSONAPI request" do
      expect(serializer.as_json[:quiz][:quiz_submission_html_url]).to eq(
        controller.send(:course_quiz_submission_html_url, context.id, quiz.id)
      )
      expect(controller).to receive(:accepts_jsonapi?).at_least(:once).and_return false
      expect(serializer.as_json[:quiz]).not_to have_key :quiz_submission_html_url
    end
  end

  describe "quiz_submissions_zip_url" do
    it "includes a url to download all files" do
      expect(controller).to receive(:accepts_jsonapi?).at_least(:once).and_return true
      expect(serializer).to receive(:user_may_grade?).at_least(:once).and_return true
      expect(serializer).to receive(:has_file_uploads?).at_least(:once).and_return true
      expect(serializer.as_json[:quiz][:quiz_submissions_zip_url]).to eq(
        "http://example.com/courses/1/quizzes/1/submissions?zip=1"
      )
    end

    it "doesn't if it's not a JSON-API request" do
      expect(controller).to receive(:accepts_jsonapi?).at_least(:once).and_return false
      expect(serializer).to receive(:user_may_grade?).at_least(:once).and_return true
      expect(serializer.as_json[:quiz]).not_to have_key :quiz_submissions_zip_url
    end

    it "doesn't if the user may not grade" do
      expect(controller).to receive(:accepts_jsonapi?).at_least(:once).and_return true
      expect(serializer).to receive(:user_may_grade?).at_least(:once).and_return false
      expect(serializer.as_json[:quiz]).not_to have_key :quiz_submissions_zip_url
    end

    it "doesn't if the quiz has no file upload questions" do
      expect(controller).to receive(:accepts_jsonapi?).at_least(:once).and_return true
      expect(serializer).to receive(:user_may_grade?).at_least(:once).and_return true
      expect(serializer).to receive(:has_file_uploads?).at_least(:once).and_return false
      expect(serializer.as_json[:quiz]).not_to have_key :quiz_submissions_zip_url
    end
  end

  describe "permissions" do
    it "serializes permissions" do
      expect(serializer.as_json[:quiz][:permissions]).to eq({
                                                              read: true,
                                                              submit: true,
                                                              review_grades: true,
                                                              create: true,
                                                              update: true,
                                                              read_statistics: true,
                                                              view_answer_audits: true,
                                                              manage: true,
                                                              manage_assign_to: true,
                                                              delete: true,
                                                              grade: true,
                                                              preview: true
                                                            })
    end
  end

  it "displays overridden dates for students" do
    course_with_student(active_all: true)
    course_quiz(true)
    serializer = quiz_serializer(scope: @student)
    student_overrides = {
      due_at: 5.minutes.from_now,
      lock_at: nil,
      unlock_at: 3.minutes.from_now
    }

    allow(serializer).to receive(:due_dates).and_return [student_overrides]

    output = serializer.as_json[:quiz]
    expect(output).not_to have_key :all_dates

    %i[due_at lock_at unlock_at].each do |key|
      expect(output[key]).to eq student_overrides[key]
    end
  end

  it "displays quiz dates for students if not overridden" do
    student_overrides = []

    expect(quiz).to receive(:due_at).at_least(:once).and_return 5.minutes.from_now
    expect(quiz).to receive(:lock_at).at_least(:once).and_return nil
    expect(quiz).to receive(:unlock_at).at_least(:once).and_return 3.minutes.from_now
    allow(serializer).to receive(:due_dates).and_return student_overrides

    output = serializer.as_json[:quiz]

    %i[due_at lock_at unlock_at].each do |key|
      expect(output[key]).to eq quiz.send(key)
    end
  end

  it "includes all_dates for teachers and observers" do
    quiz.due_at = 1.hour.from_now

    teacher_overrides = [{
      due_at: quiz.due_at,
      lock_at: nil,
      unlock_at: nil,
      base: true
    },
                         {
                           due_at: 30.minutes.from_now,
                           lock_at: 1.hour.from_now,
                           unlock_at: 10.minutes.from_now,
                           title: "Some Section"
                         }]

    expect(quiz).to receive(:due_at).at_least(:once)
    allow(serializer).to receive_messages(all_dates: teacher_overrides, include_all_dates?: true)

    output = serializer.as_json[:quiz]
    expect(output).to have_key :all_dates
    expect(output[:all_dates].length).to eq 2
    expect(output[:all_dates].detect { |e| e[:base] }).to eq teacher_overrides[0]
    expect(output[:all_dates].detect { |e| !e[:base] }).to eq teacher_overrides[1]
    expect(output[:due_at]).to eq quiz.due_at
  end

  describe "only_visible_to_overrides" do
    context "as a teacher" do
      before :once do
        course_with_teacher(active_all: true)
        course_quiz(true)
      end

      it "returns the value for DA" do
        @quiz.only_visible_to_overrides = true
        json = quiz_serializer(scope: @teacher).as_json
        expect(json[:quiz][:only_visible_to_overrides]).to be_truthy

        @quiz.only_visible_to_overrides = false
        json = quiz_serializer(scope: @teacher).as_json
        expect(json[:quiz]).to have_key :only_visible_to_overrides
        expect(json[:quiz][:only_visible_to_overrides]).to be_falsey
      end
    end

    context "as a student" do
      before :once do
        course_with_student(active_all: true)
        course_quiz(true)
      end

      it "is not in the hash" do
        @quiz.only_visible_to_overrides = true
        json = quiz_serializer(scope: @student).as_json
        expect(json[:quiz]).not_to have_key :only_visible_to_overrides
      end
    end
  end

  it "includes anonymous_submisions if quiz is a survey quiz" do
    expect(json.keys).to_not include(:anonymous_submissions)

    quiz.quiz_type = "survey"
    quiz.anonymous_submissions = true
    new_json = quiz_serializer.as_json[:quiz]
    expect(new_json[:anonymous_submissions]).to be true
  end

  it "does not include question_types" do
    expect(json.keys).not_to include(:question_types)
  end

  describe "important_dates" do
    it "returns false if the quiz does not have an assignment" do
      expect(quiz.assignment).to be_nil
      expect(json[:important_dates]).to be false
    end

    it "returns the value of assignment#important_dates" do
      assignment = Assignment.new
      assignment.id = 1
      assignment.context_id = @context.id
      assignment.important_dates = true
      allow(@quiz).to receive(:assignment).and_return assignment

      expect(@serializer.as_json[:quiz][:important_dates]).to be true
    end
  end

  describe "enabled_course_paces" do
    it "when enabled, quiz is 'in_paced_course'" do
      @context.enable_course_paces = true
      expect(@serializer.as_json[:quiz][:in_paced_course]).to be true
    end

    it "when disabled, quiz is not 'in_paced_course'" do
      @context.enable_course_paces = false
      expect(@serializer.as_json[:quiz][:in_paced_course]).to be false
    end
  end
end
