# 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 AssessmentQuestion do
  before :once do
    course_factory(active_all: true)
    @bank = @course.assessment_question_banks.create!(title: "Test Bank")
  end

  def attachment_in_course(course)
    Attachment.create!(
      filename: "test.jpg",
      display_name: "test.jpg",
      uploaded_data: StringIO.new("psych!"),
      folder: Folder.unfiled_folder(course),
      context: course
    )
  end

  it "creates a new instance given valid attributes" do
    expect do
      assessment_question_model(bank: AssessmentQuestionBank.create!(context: Course.create!))
    end.not_to raise_error
  end

  it "infer_defaultses from question_data before validation" do
    @question = assessment_question_model(bank: AssessmentQuestionBank.create!(context: Course.create!))
    @question.name = "1" * 300
    @question.save(validate: false)
    expect(@question.name.length).to eq 300

    @question.question_data[:question_name] = "valid name"
    @question.save!
    expect(@question).to be_valid
    expect(@question.name).to eq @question.question_data[:question_name]
  end

  it "translates links to be readable when creating the assessment question" do
    @attachment = attachment_in_course(@course)
    data = { "name" => "Hi", "question_text" => "Translate this: <img src='/courses/#{@course.id}/files/#{@attachment.id}/download'>", "answers" => [{ "id" => 1 }, { "id" => 2 }] }
    @question = @bank.assessment_questions.create!(question_data: data)

    @clone = @question.attachments.where(root_attachment: @attachment).first

    expect(@question.reload.question_data["question_text"]).to eq "Translate this: <img src='/assessment_questions/#{@question.id}/files/#{@clone.id}/download?verifier=#{@clone.uuid}'>"
  end

  it "translates links relative path url" do
    @attachment = attachment_in_course(@course)
    data = { "name" => "Hi", "question_text" => "Translate this: <img src='/courses/#{@course.id}/file_contents/course%20files/unfiled/test.jpg'>", "answers" => [{ "id" => 1 }, { "id" => 2 }] }
    @question = @bank.assessment_questions.create!(question_data: data)

    @clone = @question.attachments.where(root_attachment: @attachment).first

    expect(@question.reload.question_data["question_text"]).to eq "Translate this: <img src='/assessment_questions/#{@question.id}/files/#{@clone.id}/download?verifier=#{@clone.uuid}'>"
  end

  it "handles existing query string parameters" do
    @attachment = attachment_in_course(@course)
    data = { "name" => "Hi",
             "question_text" => "Translate this: <img src='/courses/#{@course.id}/files/#{@attachment.id}/download?wrap=1'> and this: <img src='/courses/#{@course.id}/file_contents/course%20files/unfiled/test.jpg?wrap=1'>",
             "answers" => [{ "id" => 1 }, { "id" => 2 }] }
    @question = @bank.assessment_questions.create!(question_data: data)

    @clone = @question.attachments.where(root_attachment: @attachment).first

    expect(@question.reload.question_data["question_text"]).to eq "Translate this: <img src='/assessment_questions/#{@question.id}/files/#{@clone.id}/download?verifier=#{@clone.uuid}&wrap=1'> and this: <img src='/assessment_questions/#{@question.id}/files/#{@clone.id}/download?verifier=#{@clone.uuid}&wrap=1'>"
  end

  it "translates multiple links in same body" do
    @attachment = attachment_in_course(@course)

    data = { "name" => "Hi", "question_text" => "Translate this: <img src='/courses/#{@course.id}/files/#{@attachment.id}/download'> and this: <img src='/courses/#{@course.id}/file_contents/course%20files/unfiled/test.jpg'>", "answers" => [{ "id" => 1 }, { "id" => 2 }] }
    @question = @bank.assessment_questions.create!(question_data: data)

    @clone = @question.attachments.where(root_attachment: @attachment).first

    expect(@question.reload.question_data["question_text"]).to eq "Translate this: <img src='/assessment_questions/#{@question.id}/files/#{@clone.id}/download?verifier=#{@clone.uuid}'> and this: <img src='/assessment_questions/#{@question.id}/files/#{@clone.id}/download?verifier=#{@clone.uuid}'>"
  end

  it "translates user files" do
    user_file = @teacher.attachments.create!(uploaded_data: fixture_file_upload("docs/doc.doc", "application/msword", true))
    data = { "name" => "Hi", "question_text" => "Translate this: <img src='/users/#{@teacher.id}/files/#{user_file.id}/download'>", "answers" => [{ "id" => 1 }, { "id" => 2 }] }

    @question = @bank.assessment_questions.create!(question_data: data, current_user: @teacher)
    @clone = @question.attachments.where(root_attachment: user_file).first
    expect(@question.reload.question_data["question_text"]).to eq "Translate this: <img src='/assessment_questions/#{@question.id}/files/#{@clone.id}/download?verifier=#{@clone.uuid}'>"
  end

  it "does not allow another users without proper permission to user file to clone it" do
    other_user = user_model
    user_file = @teacher.attachments.create!(uploaded_data: fixture_file_upload("docs/doc.doc", "application/msword", true))
    data = { "name" => "Hi", "question_text" => "Translate this: <img src='/users/#{@teacher.id}/files/#{user_file.id}/download'>", "answers" => [{ "id" => 1 }, { "id" => 2 }] }

    @question = @bank.assessment_questions.create!(question_data: data, current_user: other_user)
    @clone = @question.attachments.where(root_attachment: user_file).first
    expect(@clone).to be_nil
  end

  context "when disable_file_verifier_access feature flag is enabled" do
    it "translates multiple links in same body and would not add verifiers" do
      @attachment = attachment_in_course(@course)
      @attachment.root_account.enable_feature!(:disable_file_verifier_access)

      data = { "name" => "Hi", "question_text" => "Translate this: <img src='/courses/#{@course.id}/files/#{@attachment.id}/download'> and this: <img src='/courses/#{@course.id}/file_contents/course%20files/unfiled/test.jpg'>", "answers" => [{ "id" => 1 }, { "id" => 2 }] }
      @question = @bank.assessment_questions.create!(question_data: data)

      @clone = @question.attachments.where(root_attachment: @attachment).first

      expect(@question.reload.question_data["question_text"]).to eq "Translate this: <img src='/assessment_questions/#{@question.id}/files/#{@clone.id}/download'> and this: <img src='/assessment_questions/#{@question.id}/files/#{@clone.id}/download'>"
    end

    it "translates user files and would not add verifiers" do
      user_file = @teacher.attachments.create!(uploaded_data: fixture_file_upload("docs/doc.doc", "application/msword", true))
      data = { "name" => "Hi", "question_text" => "Translate this: <img src='/users/#{@teacher.id}/files/#{user_file.id}/download'>", "answers" => [{ "id" => 1 }, { "id" => 2 }] }
      user_file.root_account.enable_feature!(:disable_file_verifier_access)

      @question = @bank.assessment_questions.create!(question_data: data, current_user: @teacher)
      @clone = @question.attachments.where(root_attachment: user_file).first
      expect(@question.reload.question_data["question_text"]).to eq "Translate this: <img src='/assessment_questions/#{@question.id}/files/#{@clone.id}/download'>"
    end
  end

  it "translates links to be readable w/ verifier" do
    @attachments = {}
    attachment_tag = lambda do |key|
      @attachments[key] ||= []
      a = @course.attachments.build(filename: "foo-#{key}.gif")
      a.content_type = "image/gif"
      a.save!
      @attachments[key] << a
      "<img src=\"/courses/#{@course.id}/files/#{a.id}/download\">"
    end
    data = {
      name: "test question",
      question_type: "multiple_choice_question",
      question_text: "which ones are like this one? #{attachment_tag.call([:question_text])} what about: #{attachment_tag.call([:question_text])}",
      correct_comments: "yay! #{attachment_tag.call([:correct_comments])}",
      incorrect_comments: "boo! #{attachment_tag.call([:incorrect_comments])}",
      neutral_comments: "meh. #{attachment_tag.call([:neutral_comments])}",
      text_after_answers: "oh btw #{attachment_tag.call([:text_after_answers])}",
      answers: [
        { weight: 1,
          text: "A",
          html: "A #{attachment_tag.call([:answers, 0, :html])}",
          comments_html: "yeppers #{attachment_tag.call([:answers, 0, :comments_html])}" },
        { weight: 1,
          text: "B",
          html: "B #{attachment_tag.call([:answers, 1, :html])}",
          comments_html: "yeppers #{attachment_tag.call([:answers, 1, :comments_html])}" }
      ]
    }

    serialized_data_before = Marshal.dump(data)

    @question = @bank.assessment_questions.create!(question_data: data)

    @attachment_clones = @attachments.transform_values { |ary| ary.map { |a| @question.attachments.where(root_attachment_id: a).first } }

    @attachment_clones.each do |key, ary|
      string = @question.question_data.dig(*key)
      matches = string.scan %r{/assessment_questions/\d+/files/\d+/download\?verifier=\w+}
      expect(matches.length).to eq ary.length
      matches.each_with_index do |match, index|
        a = ary[index]
        expect(match).to eq "/assessment_questions/#{@question.id}/files/#{a.id}/download?verifier=#{a.uuid}"
      end
    end

    # the original data hash should not have changed during the link translation
    serialized_data_after = Marshal.dump(data)
    expect(serialized_data_before).to eq serialized_data_after
  end

  it "does not drop non-string/array/hash data types when translate links" do
    bank = @course.assessment_question_banks.create!(title: "Test Bank")

    data = {
      name: "mc question",
      question_type: "multiple_choice_question",
      question_text: "text text text",
      points_possible: "10",
      correct_comments: "",
      incorrect_comments: "",
      answers: {
        "answer_0" => { answer_weight: 100, answer_text: "1", id: "0", answer_comments: "hi there" }
      }
    }

    question = bank.assessment_questions.create!(question_data: data)
    expect(question.question_data[:points_possible]).to eq "10"
    data[:points_possible] = "50"
    question.form_question_data = data
    question.save
    expect(question.question_data.class).to eq ActiveSupport::HashWithIndifferentAccess
    expect(question.question_data[:points_possible]).to eq 50
    expect(question.question_data[:answers][0][:weight]).to eq 100
    expect(question.question_data[:answers][0][:id]).not_to be_nil
    expect(question.question_data[:assessment_question_id]).to eq question.id
  end

  it "always returns a HashWithIndifferentAccess and allow editing" do
    data = {
      name: "mc question",
      question_type: "multiple_choice_question",
      question_text: "text text text",
      points_possible: "10",
      answers: {
        "answer_0" => { answer_weight: 100, answer_text: "1", id: "0", answer_comments: "hi there" }
      }
    }

    question = @bank.assessment_questions.create!(question_data: data)
    expect(question.question_data.class).to eq ActiveSupport::HashWithIndifferentAccess

    question.question_data = data
    expect(question.question_data.class).to eq ActiveSupport::HashWithIndifferentAccess

    data = question.question_data
    data[:name] = "new name"

    expect(question.question_data[:name]).to eq "new name"
    expect(data.object_id).to eq question.question_data.object_id
  end

  describe ".find_or_create_quiz_questions" do
    let(:assessment_question) { assessment_question_model(bank: AssessmentQuestionBank.create!(context: Course.create!)) }
    let(:quiz) { quiz_model }

    it "creates a quiz_question when one does not exist" do
      expect do
        AssessmentQuestion.find_or_create_quiz_questions([assessment_question], quiz.id, nil)
      end.to change { Quizzes::QuizQuestion.count }.by(1)
    end

    it "finds an existing quiz_question" do
      qq = AssessmentQuestion.find_or_create_quiz_questions([assessment_question], quiz.id, nil).first

      expect do
        qq2 = AssessmentQuestion.find_or_create_quiz_questions([assessment_question], quiz.id, nil).first
        expect(qq2.id).to eql(qq.id)
      end.to_not change { AssessmentQuestion.count }
    end

    it "finds and update an out of date quiz_question" do
      aq = assessment_question
      qq = AssessmentQuestion.find_or_create_quiz_questions([aq], quiz.id, nil).first

      aq = AssessmentQuestion.find(aq.id)
      aq.name = "changed"
      aq.with_versioning(&:save!)

      expect(qq.assessment_question_version).to_not eql(aq.version_number)

      qq2 = AssessmentQuestion.find_or_create_quiz_questions([aq], quiz.id, nil).first
      aq = AssessmentQuestion.find(aq.id)
      expect(qq.assessment_question_version).to_not eql(qq2.assessment_question_version)
      expect(qq2.assessment_question_version).to eql(aq.version_number)
    end

    it "grabs the first match by ID order" do
      # consistent ordering is good for preventing deadlocks
      questions = []
      3.times { questions << assessment_question.create_quiz_question(quiz.id) }
      smallest_id_question = questions.min_by(&:id)
      qq = AssessmentQuestion.find_or_create_quiz_questions([assessment_question], quiz.id, nil).first
      expect(qq.id).to eq(smallest_id_question.id)
    end
  end

  context "root_account_id" do
    it "uses root_account value from account" do
      question = assessment_question_model(bank: AssessmentQuestionBank.create!(context: Course.create!))

      expect(question.root_account_id).to eq Account.default.id
    end
  end
end
