# frozen_string_literal: true

describe QuestionnairesController do
  let(:questionnaire) do
    build(id: 1, name: 'questionnaire', ta_id: 8, course_id: 1, private: false, min_question_score: 0, max_question_score: 5, type: 'ReviewQuestionnaire')
  end
  let(:questionnaire) { build(:questionnaire) }
  let(:quiz_questionnaire) { build(:questionnaire, type: 'QuizQuestionnaire') }
  let(:review_questionnaire) { build(:questionnaire, type: 'ReviewQuestionnaire') }
  let(:question) { build(:question, id: 1) }
  let(:admin) { build(:admin) }
  let(:instructor) { build(:instructor, id: 6) }
  let(:instructor2) { build(:instructor, id: 66) }
  let(:ta) { build(:teaching_assistant, id: 8) }
  let(:questionnaire1) { build(questionnaire, id: 1, assignment_id: 1, questionnaire_id: 1, used_in_round: 1) }
  let(:questionnaire2) { build(questionnaire, id: 2, assignment_id: 1, questionnaire_id: 2, used_in_round: 2) }
  let(:assignment) { build(assignment, id: 1) }
  let(:due_date1) { build(due_date, id: 1, due_at: '2019-11-30 23:30:12', deadline_type_id: 1, parent_id: 1, round: 1) }
  let(:due_date2) { build(due_date, id: 2, due_at: '2500-12-30 23:30:12', deadline_type_id: 2, parent_id: 1, round: 1) }
  let(:due_date3) { build(due_date, id: 3, due_at: '2019-01-30 23:30:12', deadline_type_id: 1, parent_id: 1, round: 2) }
  let(:due_date4) { build(due_date, id: 4, due_at: '2019-02-28 23:30:12', deadline_type_id: 2, parent_id: 1, round: 2) }
  let(:assignment_questionnaire1) { build(assignment_questionnaire, id: 1, assignment_id: 1, questionnaire_id: 1, used_in_round: 1) }
  let(:assignment_questionnaire2) { build(assignment_questionnaire, id: 2, assignment_id: 1, questionnaire_id: 2, used_in_round: 2) }
  before(:each) do
    allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)
    stub_current_user(instructor, instructor.role.name, instructor.role)
  end

  def check_access(username)
    stub_current_user(username, username.role.name, username.role)
    expect(controller.send(:action_allowed?))
  end

  describe '#action_allowed?' do
    let(:questionnaire) { build(:questionnaire, id: 1) }
    let(:instructor) { build(:instructor, id: 1) }
    let(:ta) { build(:teaching_assistant, id: 10, parent_id: 66) }

    context 'when request_params action is edit or update' do
      before(:each) do
        controller.params = { id: '1', action: 'edit' }
        controller.request.session[:user] = instructor
      end

      context 'when the role name of current user is super admin or admin' do
        it 'allows certain action' do
          check_access(admin).to be true
        end
      end

      context 'when current user is the instructor of current questionnaires' do
        it 'allows certain action' do
          check_access(instructor).to be true
        end
      end

      context 'when current user is the ta of the course which current questionnaires belongs to' do
        it 'allows certain action' do
          teaching_assistant = create(:teaching_assistant)
          stub_current_user(teaching_assistant, teaching_assistant.role.name, teaching_assistant.role)
          course = create(:course)
          TaMapping.create(ta_id: teaching_assistant.id, course_id: course.id)
          check_access(teaching_assistant).to be true
        end
      end

      context 'when current user is a ta but not the ta of the course which current questionnaires belongs to' do
        it 'does not allow certain action' do
          # The questionnaire is associated with the first instructor
          # A factory created course will associate itself with the first instructor
          # So here we want the TA on a course that explicitly has some other instructor
          # Otherwise the TA will be indirectly associated with the questionnaire
          teaching_assistant = create(:teaching_assistant)
          stub_current_user(teaching_assistant, teaching_assistant.role.name, teaching_assistant.role)
          instructor1 = create(:instructor, name: 'test_instructor1')
          instructor2 = create(:instructor, name: 'test_instructor2')
          course = create(:course, instructor_id: instructor2.id)
          TaMapping.create(ta_id: teaching_assistant.id, course_id: course.id)
          check_access(teaching_assistant).to be false
        end
      end

      context 'when current user is the instructor of the course which current questionnaires belongs to' do
        it 'allows certain action' do
          allow(Course).to receive(:find).with(1).and_return(double('Course', instructor_id: 6))
          check_access(instructor).to be true
        end
      end

      context 'when current user is an instructor but not the instructor of current course or current questionnaires' do
        it 'does not allow certain action' do
          allow(Course).to receive(:find).with(1).and_return(double('Course', instructor_id: 66))
          check_access(instructor2).to be false
        end
      end
    end
    context 'when request_params action is not edit and update' do
      before(:each) do
        controller.params = { id: '1', action: 'new' }
      end

      context 'when the role current user is super admin/admin/instructor/ta' do
        it 'allows certain action except edit and update' do
          check_access(admin).to be true
        end
      end
    end
  end
  describe '#copy' do
    it 'redirects to view page of copied questionnaire' do
      allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)
      allow(Question).to receive(:where).with(questionnaire_id: '1').and_return([question])
      allow(instructor).to receive(:instructor_id).and_return(6)
      question_advice = build(:question_advice)
      allow(QuestionAdvice).to receive(:where).with(question_id: 1).and_return([question_advice])
      tree_folder = double('TreeFolder', id: 1)
      allow(TreeFolder).to receive(:find_by).with(name: 'Review').and_return(tree_folder)
      allow(FolderNode).to receive(:find_by).with(node_object_id: 1).and_return(double('FolderNode', id: 1))
      allow(QuestionnaireNode).to receive(:find_or_create_by).with(parent_id: 1, node_object_id: 2).and_return(double('QuestionnaireNode'))
      allow_any_instance_of(QuestionnairesController).to receive(:undo_link).with(any_args).and_return('')
      request_params = { id: 1 }
      user_session = { user: instructor }
      get :copy, params: request_params, session: user_session
      expect(response).to redirect_to('/questionnaires/view?id=2')
      expect(controller.instance_variable_get(:@questionnaire).name).to eq('Copy of ' + questionnaire.name)
      expect(controller.instance_variable_get(:@questionnaire).private).to eq false
      expect(controller.instance_variable_get(:@questionnaire).min_question_score).to eq 0
      expect(controller.instance_variable_get(:@questionnaire).max_question_score).to eq 5
      expect(controller.instance_variable_get(:@questionnaire).type).to eq 'ReviewQuestionnaire'
      expect(controller.instance_variable_get(:@questionnaire).display_type).to eq 'Review'
      expect(controller.instance_variable_get(:@questionnaire).instructor_id).to eq 6
    end
  end

  describe '#view' do
    it 'renders questionnaires#view page' do
      allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire)
      request_params = { id: 1 }
      get :view, params: request_params
      expect(response).to render_template(:view)
    end
  end

  describe '#new' do
    context 'when request_params[:model] has whitespace in it' do
      it 'creates new questionnaire object and renders questionnaires#new page' do
        request_params = { model: 'Assignment SurveyQuestionnaire' }
        get :new, params: request_params
        expect(response).to render_template(:new)
      end
    end

    context 'when request_params[:model] does not have whitespace in it' do
      it 'creates new questionnaire object and renders questionnaires#new page' do
        request_params = { model: 'ReviewQuestionnaire' }
        get :new, params: request_params
        expect(response).to render_template(:new)
      end
    end

    context 'when the questionnaire is a bookmark rating rubric' do
      it 'creates new questionnaire object and renders questionnaires#new page' do
        request_params = { model: 'BookmarkRatingQuestionnaire' }
        get :new, params: request_params
        expect(response).to render_template(:new)
      end
    end

    context 'when the questionnaire is a bookmark rating rubric and has whitespace' do
      it 'creates new questionnaire object and renders questionnaires#new page' do
        request_params = { model: 'Bookmark RatingQuestionnaire' }
        get :new, params: request_params
        expect(response).to render_template(:new)
      end
    end
  end

  describe '#create' do
    it 'redirects to questionnaires#edit page after create a new questionnaire' do
      request_params = { questionnaire: { name: 'test questionnaire',
                                          private: false,
                                          min_question_score: 0,
                                          max_question_score: 5,
                                          type: 'ReviewQuestionnaire' } }
      user_session = { user: instructor }
      tree_folder = double('TreeFolder', id: 1)
      allow(TreeFolder).to receive_message_chain(:where, :first).with(['name like ?', 'Review']).with(no_args).and_return(tree_folder)
      allow(FolderNode).to receive(:find_by).with(node_object_id: 1).and_return(double('FolderNode', id: 1))
      allow(QuestionnaireNode).to receive(:create).with(parent_id: 1, node_object_id: 1, type: 'QuestionnaireNode').and_return(double('QuestionnaireNode'))
      post :create, params: request_params, session: user_session
      expect(flash[:success]).to eq('You have successfully created a questionnaire!')
      expect(response).to redirect_to('/questionnaires/1/edit')
      expect(controller.instance_variable_get(:@questionnaire).private).to eq false
      expect(controller.instance_variable_get(:@questionnaire).name).to eq 'test questionnaire'
      expect(controller.instance_variable_get(:@questionnaire).min_question_score).to eq 0
      expect(controller.instance_variable_get(:@questionnaire).max_question_score).to eq 5
      expect(controller.instance_variable_get(:@questionnaire).type).to eq 'ReviewQuestionnaire'
      expect(controller.instance_variable_get(:@questionnaire).display_type).to eq 'Review'
      expect(controller.instance_variable_get(:@questionnaire).instructor_id).to eq 6
    end
  end

  describe '#edit' do
    context 'when @questionnaire is not nil' do
      it 'renders the questionnaires#edit page' do
        allow(Questionnaire).to receive(:find).with('1').and_return(double('Questionnaire', instructor_id: 6))
        user_session = { user: instructor }
        request_params = { id: 1 }
        get :edit, params: request_params, session: user_session
        expect(response).to render_template(:edit)
      end
    end

    context 'when @questionnaire is nil' do
      it 'redirects to root page' do
        allow(Questionnaire).to receive(:find).with('666').and_return(nil)
        user_session = { user: instructor }
        request_params = { id: 666 }
        get :edit, params: request_params, session: user_session
        expect(response).to redirect_to('/')
      end
    end
  end

  describe '#update' do
    before(:each) do
      @questionnaire1 = double('Questionnaire', id: 1)
      allow(Questionnaire).to receive(:find).with('1').and_return(@questionnaire1)
      @request_params = { id: 1,
                          questionnaire: { name: 'test questionnaire',
                                           instructor_id: 6,
                                           private: 0,
                                           min_question_score: 0,
                                           max_question_score: 5,
                                           type: 'ReviewQuestionnaire',
                                           display_type: 'Review',
                                           instructor_loc: '' } }
      @request_params_with_question = { id: 1,
                                        questionnaire: { name: 'test questionnaire',
                                                         instructor_id: 6,
                                                         private: 0,
                                                         min_question_score: 0,
                                                         max_question_score: 5,
                                                         type: 'ReviewQuestionnaire',
                                                         display_type: 'Review',
                                                         instructor_loc: '' },
                                        question: { '1' => { seq: 66.0,
                                                             txt: 'WOW',
                                                             weight: 10,
                                                             size: '50,3',
                                                             max_label: 'Strong agree',
                                                             min_label: 'Not agree' } } }
    end
    context 'successfully updates the attributes of questionnaire' do
      it 'redirects to questionnaires#edit page after updating' do
        allow(@questionnaire1).to receive(:update_attributes).with(any_args).and_return(true)
        # need complete request_params hash to handle strong parameters
        post :update, params: @request_params
        expect(flash[:success]).to eq 'The questionnaire has been successfully updated!'
        expect(response).to redirect_to('/questionnaires/1/edit')
      end
    end

    context 'have some errors when updating the attributes of questionnaire' do
      it 'redirects to questionnaires#edit page after updating' do
        allow(@questionnaire1).to receive(:update_attributes).with(any_args).and_raise('This is an error!')
        post :update, params: @request_params
        expect(flash[:error].to_s).to eq 'This is an error!'
        expect(response).to redirect_to('/questionnaires/1/edit')
      end
    end

    context 'successfully updates the questions in a questionnaire' do
      it 'redirects to questionnaires#edit page after saving all questions' do
        allow(Question).to receive(:find).with('1').and_return(question)
        allow(question).to receive(:save).and_return(true)
        allow(@questionnaire1).to receive(:update_attributes).with(any_args).and_return(true)
        post :update, params: @request_params_with_question
        expect(flash[:success]).to eq('The questionnaire has been successfully updated!')
        expect(response).to redirect_to('/questionnaires/1/edit')
      end
    end

    context 'when request_params[:view_advice] is not nil' do
      it 'redirects to advice#edit_advice page' do
        request_params = { id: 1,
                           view_advice: true }
        post :update, params: request_params
        expect(response).to redirect_to('/advice/edit_advice?id=1')
      end
    end

    context 'when request_params[:add_new_questions] is not nil' do
      it 'redirects to questionnaire#add_new_questions' do
        request_params = { id: 1,
                           add_new_questions: true,
                           new_question: { total_num: 2,
                                           type: 'Criterion' } }
        post :update, params: request_params
        expect(response).to redirect_to action: 'add_new_questions', id: request_params[:id], question: request_params[:new_question]
      end
    end
  end

  describe '#delete' do
    context 'when @questionnaire.assignments returns non-empty array' do
      it 'sends the error message to flash[:error]' do
        questionnaire1 = double('Questionnaire',
                                name: 'test questionnaire',
                                assignments: [double('Assignment',
                                                     name: 'test assignment')])
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire1)
        request_params = { id: 1 }
        get :delete, params: request_params
        expect(flash[:error]).to eq('The assignment <b>test assignment</b> uses this questionnaire. Are sure you want to delete the assignment?')
        expect(response).to redirect_to('/tree_display/list')
      end
    end

    context 'when question.answers returns non-empty array' do
      it 'sends the error message to flash[:error]' do
        questionnaire1 = double('Questionnaire',
                                name: 'test questionnaire',
                                assignments: [],
                                questions: [double('Question',
                                                   answers: [double('Answer')])])
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire1)
        request_params = { id: 1 }
        get :delete, params: request_params
        expect(flash[:error]).to eq('There are responses based on this rubric, we suggest you do not delete it.')
        expect(response).to redirect_to('/tree_display/list')
      end
    end

    context 'when @questionnaire.assignments and question.answers return empty arrays' do
      it 'deletes all objects related to current questionnaire' do
        advices = [double('QuestionAdvice')]
        question = double('Question', answers: [], question_advices: advices)
        questionnaire_node = double('QuestionnaireNode')
        questionnaire1 = double('Questionnaire',
                                name: 'test questionnaire',
                                assignments: [],
                                questions: [question],
                                questionnaire_node: questionnaire_node)
        allow(Questionnaire).to receive(:find).with('1').and_return(questionnaire1)
        allow(advices).to receive(:each).with(any_args).and_return(true)
        allow(question).to receive(:delete).and_return(true)
        allow(questionnaire_node).to receive(:delete).and_return(true)
        allow(questionnaire1).to receive(:delete).and_return(true)
        allow_any_instance_of(QuestionnairesController).to receive(:undo_link).with(any_args).and_return(true)
        request_params = { id: 1 }
        get :delete, params: request_params
        expect(flash[:error]).to eq nil
        expect(response).to redirect_to('/tree_display/list')
      end
    end
  end

  describe '#add_new_questions' do
    let(:criterion) { Criterion.new(id: 2, weight: 1, max_label: '', min_label: '', size: '', alternatives: '') }
    let(:dropdown) { Dropdown.new(id: 3, size: '', alternatives: '') }

    context 'when adding ScoredQuestion' do
      it 'redirects to questionnaires#edit page after adding new questions' do
        allow(Questionnaire).to receive(:find).with('1').and_return(double('Questionnaire', id: 1, questions: [criterion]))
        allow(question).to receive(:seq).and_return(0)
        allow_any_instance_of(Array).to receive(:ids).and_return([2]) # need to stub since .ids isn't recognized in the context of testing
        allow(question).to receive(:save).and_return(true)
        request_params = { id: 1,
                           question: { total_num: 2,
                                       type: 'Criterion' } }
        post :add_new_questions, params: request_params
        expect(response).to redirect_to('/questionnaires/1/edit')
      end
    end

    context 'when adding unScoredQuestion' do
      it 'redirects to questionnaires#edit page after adding new questions' do
        allow(Questionnaire).to receive(:find).with('1').and_return(double('Questionnaire', id: 1, questions: [dropdown]))
        allow_any_instance_of(Array).to receive(:ids).and_return([3]) # need to stub since .ids isn't recognized in the context of testing
        allow(question).to receive(:save).and_return(true)
        request_params = { id: 1,
                           question: { total_num: 2,
                                       type: 'Dropdown' } }
        post :add_new_questions, params: request_params
        expect(response).to redirect_to('/questionnaires/1/edit')
      end
    end

    context 'when add_new_questions is called and the change is not in the period.' do
      it 'AnswerHelper.in_active_period should be called to check if this change is in the period.' do
        allow(AnswerHelper).to receive(:in_active_period).with('1').and_return(false)
        expect(AnswerHelper).to receive(:in_active_period).with('1')
        request_params = { id: 1,
                           question: { total_num: 2,
                                       type: 'Criterion' } }
        post :add_new_questions, params: request_params
      end
    end

    context 'when add_new_questions is called and the change is in the period.' do
      it 'AnswerHelper.delete_existing_responses should be called to check if this change is in the period.' do
        allow(AnswerHelper).to receive(:in_active_period).with('1').and_return(true)
        allow(AnswerHelper).to receive(:delete_existing_responses).with([], '1')
        expect(AnswerHelper).to receive(:delete_existing_responses).with([], '1')
        request_params = { id: 1,
                           question: { total_num: 2,
                                       type: 'Criterion' } }
        post :add_new_questions, params: request_params
      end
    end
  end

  describe '#save_all_questions' do
    context 'when request_params[:save] is not nil, params: request_params[:view_advice] is nil' do
      it 'redirects to questionnaires#edit page after saving all questions' do
        allow(Question).to receive(:find).with('1').and_return(question)
        allow(question).to receive(:save).and_return(true)
        request_params = { id: 1,
                           save: true,
                           question: { '1' => { seq: 66.0,
                                                txt: 'WOW',
                                                weight: 10,
                                                size: '50,3',
                                                max_label: 'Strong agree',
                                                min_label: 'Not agree' } } }
        post :save_all_questions, params: request_params
        expect(flash[:success]).to eq('All questions have been successfully saved!')
        expect(response).to redirect_to('/questionnaires/1/edit')
      end
    end

    context 'when request_params[:save] is nil, params: request_params[:view_advice] is not nil' do
      it 'redirects to advice#edit_advice page' do
        request_params = { id: 1,
                           view_advice: true,
                           question: {} }
        post :save_all_questions, params: request_params
        expect(response).to redirect_to('/advice/edit_advice?id=1')
      end
    end
  end
end
