# frozen_string_literal: true

#
# Copyright (C) 2017 - 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 "lti2_api_spec_helper"
require_relative "../api_spec_helper"

module Lti
  describe UsersApiController, type: :request do
    include_context "lti2_api_spec_helper"

    let(:authorized_services) do
      [
        { "service" => "vnd.Canvas.User", "action" => ["GET"], "@type" => "RestServiceProfile" },
        { "service" => "vnd.Canvas.GroupIndex", "action" => ["GET"], "@type" => "RestServiceProfile" }
      ]
    end

    let(:assignment) do
      a = course.assignments.new(title: "some assignment")
      a.workflow_state = "published"
      a.tool_settings_tool = message_handler
      a.save!
      a
    end

    before do
      message_handler.update(capabilities: [Lti::ResourcePlacement::SIMILARITY_DETECTION_LTI2])
      tool_proxy.raw_data["security_contract"]["tool_service"] = authorized_services
      tool_proxy.save!
      assignment.tool_settings_tool = message_handler
      assignment.save!
    end

    describe "#show" do
      let(:service_name) { UsersApiController::USER_SERVICE }
      let(:canvas_id_endpoint) { "/api/lti/users/#{student.id}" }
      let(:student) do
        course_with_student(active_all: true, course:)
        @student.update(lti_context_id: SecureRandom.uuid)
        @student
      end
      let(:expected_student) do
        {
          "id" => student.id,
          "created_at" => @student.created_at.iso8601,
          "name" => student.name,
          "sortable_name" => student.sortable_name,
          "short_name" => student.short_name,
          "lti_id" => student.lti_context_id,
          "email" => student.email
        }
      end

      it "verifies the tool has the required services" do
        tool_proxy.raw_data["security_contract"]["tool_service"] = []
        tool_proxy.save!
        get canvas_id_endpoint, params: { id: student.id }, headers: request_headers
        expect(response).to be_unauthorized
      end

      it "verifies the tool is associated with at least one of the user's assignments" do
        second_course = Course.create!(name: "second course")
        assignment.update!(course: second_course)
        get canvas_id_endpoint, params: { id: student.id }, headers: request_headers
        expect(response).to be_unauthorized
      end

      it "does not grant access if the tool and the user have no associated assignments" do
        assignment.destroy!
        get canvas_id_endpoint, params: { id: student.id }, headers: request_headers
        expect(response).to be_unauthorized
      end

      context "course" do
        before do
          tool_proxy.update!(context: course)
        end

        it "returns a user by lti id" do
          get canvas_id_endpoint, params: { id: student.lti_context_id }, headers: request_headers
          parsed_body = JSON.parse(response.body)
          expect(parsed_body).to eq expected_student
        end

        it "returns a user by old lti id" do
          UserPastLtiId.create!(user: student, context: course, user_lti_id: student.lti_id, user_lti_context_id: "old_lti_id", user_uuid: "old")
          get canvas_id_endpoint, params: { id: "old_lti_id" }, headers: request_headers
          parsed_body = JSON.parse(response.body)
          new_expected_student = expected_student
          new_expected_student["lti_id"] = "old_lti_id"
          expect(parsed_body).to eq expected_student
        end

        it "returns a user by Canvas id" do
          get canvas_id_endpoint, params: { id: student.id }, headers: request_headers
          parsed_body = JSON.parse(response.body)
          expect(parsed_body).to eq expected_student
        end

        it "does not grant access if the course is inactive and the user has no associated assignments" do
          id = student.id
          course.destroy!
          get canvas_id_endpoint, params: { id: }, headers: request_headers
          expect(response).to be_unauthorized
        end

        context "with sharding" do
          specs_require_sharding

          let(:user) do
            @shard1.activate do
              user_factory
            end
          end
          let(:student) do
            course_with_student(active_all: true, course:, user:)
            @student.update(lti_context_id: SecureRandom.uuid)
            @student
          end
          let(:canvas_id_endpoint) { "/api/lti/users/#{student.global_id}" }

          it "grants access for cross-shard users" do
            get canvas_id_endpoint, params: { id: student.global_id }, headers: request_headers
            parsed_body = JSON.parse(response.body)
            expect(parsed_body).to eq(
              {
                "id" => student.global_id,
                "created_at" => student.created_at.iso8601,
                "name" => student.name,
                "sortable_name" => student.sortable_name,
                "short_name" => student.short_name,
                "lti_id" => student.lti_context_id,
                "email" => student.email
              }
            )
          end
        end
      end

      context "account" do
        it "returns a user by lti id" do
          get canvas_id_endpoint, params: { id: student.lti_context_id }, headers: request_headers
          parsed_body = JSON.parse(response.body)
          expect(parsed_body).to eq expected_student
        end

        it "returns a user by Canvas id" do
          get canvas_id_endpoint, params: { id: student.id }, headers: request_headers
          parsed_body = JSON.parse(response.body)
          expect(parsed_body).to eq expected_student
        end
      end
    end

    describe "#group_index" do
      include Api::V1::User

      let(:service_name) { UsersApiController::USER_SERVICE }
      let(:course) { course_model }
      let(:group) { group_model(context: course) }
      let(:group_index_endpoint) { "/api/lti/groups/#{group.global_id}/users" }
      let(:student_lti_id) { SecureRandom.uuid }

      let(:student_one) do
        student_in_course(course: group.context)
        @student.update!(lti_context_id: student_lti_id)
        @student
      end

      let(:student_two) do
        student_in_course(course: group.context)
        @student
      end

      let(:student_three) do
        student_in_course(course: group.context)
        @student.update!(email: "student@test.com")
        @student
      end

      before do
        group.context.update!(account: tool_proxy.account)
        group.add_user(student_one, "accepted")
        group.add_user(student_two, "accepted")
        group.add_user(student_three, "accepted")
      end

      it "returns a list of users in the specified group" do
        get group_index_endpoint, headers: request_headers
        parsed_body = JSON.parse(response.body)
        expected_json = group.users.map do |user|
          user_json(user, user, nil, [], group.context, tool_includes: %w[email lti_id])
        end
        expect(parsed_body.sort_by { |u| u[:id] }).to eq(expected_json.sort_by { |u| u[:id] })
      end

      it "responds with 401 if group is not in tool context" do
        group.context.update!(account: account_model)
        get group_index_endpoint, headers: request_headers
        expect(response).to be_unauthorized
      end

      it "responds with 401 if the group is not active" do
        group.update!(workflow_state: "deleted")
        get group_index_endpoint, headers: request_headers
        expect(response).to be_unauthorized
      end

      it "responds with 404 if group is not found" do
        get "/api/lti/groups/#{group.global_id + 1}/users", headers: request_headers
        expect(response).to be_not_found
      end

      it "includes user lti id" do
        get group_index_endpoint, headers: request_headers
        parsed_body = JSON.parse(response.body)
        user_json = parsed_body.find { |u| u["id"] == student_one.id }
        expect(user_json["lti_id"]).to eq student_one.lti_context_id
      end

      it "does not include students who are not in the group" do
        student_in_course(course: group.context)
        get group_index_endpoint, headers: request_headers
        parsed_body = JSON.parse(response.body)
        expected_json = group.users.map do |user|
          user_json(user, user, nil, [], group.context, tool_includes: %w[email lti_id])
        end
        expect(parsed_body.sort_by { |u| u[:id] }).to eq(expected_json.sort_by { |u| u[:id] })
      end
    end
  end
end
