# frozen_string_literal: true

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

module Lti
  module IMS
    describe ToolSettingController, type: :request do
      let(:account) { Account.new }
      let(:product_family) { ProductFamily.create(vendor_code: "123", product_code: "abc", vendor_name: "acme", root_account: account) }
      let(:tool_proxy) do
        ToolProxy.create!(
          context: account,
          guid: SecureRandom.uuid,
          shared_secret: "abc",
          product_family:,
          product_version: "1",
          workflow_state: "disabled",
          raw_data: { "proxy" => "value" },
          lti_version: "1"
        )
      end
      let(:resource_handler) { ResourceHandler.create!(resource_type_code: "code", name: "name", tool_proxy:) }
      let(:message_handler) { MessageHandler.create(message_type: "basic-lti-launch-request", launch_path: "https://samplelaunch/blti", resource_handler:) }
      let(:access_token) { Lti::OAuth2::AccessToken.create_jwt(aud: nil, sub: tool_proxy.guid).to_s }

      before do
        allow_any_instance_of(ToolSettingController).to receive_messages(oauth_authenticated_request?: true)
        allow_any_instance_of(ToolSettingController).to receive_messages(authenticate_body_hash: true)
        allow_any_instance_of(ToolSettingController).to receive_messages(oauth_consumer_key: tool_proxy.guid)
        allow(AuthenticationMethods).to receive_messages(access_token:)
        @link_setting = ToolSetting.create(tool_proxy:, context: account, resource_link_id: "abc", custom: { link: :setting, a: 1, b: 2, c: 3 })
        @binding_setting = ToolSetting.create(tool_proxy:, context: account, custom: { binding: :setting, a: 1, b: 2, d: 4 })
        @proxy_setting = ToolSetting.create(tool_proxy:, custom: { proxy: :setting, a: 1, c: 3, d: 4 })
      end

      describe "#show" do
        it "returns toolsettings.simple when requested" do
          get "/api/lti/tool_settings/#{@link_setting.id}.json",
              params: { tool_setting_id: @link_setting },
              headers: { "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings.simple+json", "Authorization" => "bearer #{access_token}" }
          expect(response.media_type).to eq "application/vnd.ims.lti.v2.toolsettings.simple+json"
        end

        it "returns toolsettings when requested" do
          get "/api/lti/tool_settings/#{@link_setting.id}.json",
              params: { tool_setting_id: @link_setting },
              headers: { "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings+json", "Authorization" => "oauth_token" }
          expect(response.media_type).to eq "application/vnd.ims.lti.v2.toolsettings+json"
        end

        it "returns not_found if there isn't a tool setting" do
          get "/api/lti/tool_settings/3.json",
              params: { tool_setting_id: "3" },
              headers: { "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings+json", "Authorization" => "oauth_token" }
          expect(response).to have_http_status :not_found
          expect(response.body).to eq '{"status":"not_found","errors":[{"message":"not_found"}]}'
        end

        it 'returns as a bad request when bubble is something besides "all" or "distinct"' do
          get "/api/lti/tool_settings/#{@link_setting.id}.json",
              params: { tool_setting_id: @link_setting, bubble: "pop" },
              headers: { "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings+json", "Authorization" => "oauth_token" }
          expect(response).to have_http_status :bad_request
        end

        it 'returns as a bad request when bubble is "all" and the accept type is "simple"' do
          get "/api/lti/tool_settings/#{@link_setting.id}.json",
              params: { tool_setting_id: @link_setting, bubble: "all" },
              headers: { "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings.simple+json", "Authorization" => "oauth_token" }
          expect(response).to have_http_status :bad_request
        end

        context "lti_link" do
          it "returns the lti_link using resource_link_id" do
            get "/api/lti/tool_proxy/#{tool_proxy.guid}/accounts/#{account.id}/resource_link_id/#{@link_setting.resource_link_id}/tool_setting.json",
                params: {
                  tool_proxy_guid: tool_proxy.guid,
                  context_id: account.id,
                  resource_link_id: @link_setting.resource_link_id
                },
                headers: { "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings.simple+json", "Authorization" => "oauth_token" }
            expect(JSON.parse(body)).to eq({ "link" => "setting", "a" => 1, "b" => 2, "c" => 3 })
          end

          it "returns the lti link simple json" do
            get "/api/lti/tool_settings/#{@link_setting.id}.json",
                params: { tool_setting_id: @link_setting },
                headers: { "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings.simple+json", "Authorization" => "oauth_token" }
            expect(JSON.parse(body)).to eq({ "link" => "setting", "a" => 1, "b" => 2, "c" => 3 })
          end

          it "returns the lti link tool settings json with bubble distinct" do
            get "/api/lti/tool_settings/#{@link_setting.id}.json",
                params: { tool_setting_id: @link_setting, bubble: "distinct" },
                headers: { "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings+json", "Authorization" => "oauth_token" }
            json = JSON.parse(body)
            link_setting = json["@graph"].find { |setting| setting["@type"] == "LtiLink" }
            expect(link_setting["custom"]).to eq({ "link" => "setting", "a" => 1, "b" => 2, "c" => 3 })
            binding_setting = json["@graph"].find { |setting| setting["@type"] == "ToolProxyBinding" }
            expect(binding_setting["custom"]).to eq({ "binding" => "setting", "d" => 4 })
            proxy_setting = json["@graph"].find { |setting| setting["@type"] == "ToolProxy" }
            expect(proxy_setting["custom"]).to eq({ "proxy" => "setting" })
          end

          it "returns the lti link tool settings simple json with bubble distinct" do
            get "/api/lti/tool_settings/#{@link_setting.id}.json",
                params: { tool_setting_id: @link_setting, bubble: "distinct" },
                headers: { "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings.simple+json", "Authorization" => "oauth_token" }
            expect(JSON.parse(body)).to eq({ "link" => "setting", "binding" => "setting", "proxy" => "setting", "a" => 1, "b" => 2, "c" => 3, "d" => 4 })
          end

          it "bubbles up all levels" do
            get "/api/lti/tool_settings/#{@link_setting.id}.json",
                params: { tool_setting_id: @link_setting.id, bubble: "all" },
                headers: { "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings+json", "Authorization" => "oauth_token" }
            json = JSON.parse(body)
            link_setting = json["@graph"].find { |setting| setting["@type"] == "LtiLink" }
            expect(link_setting["custom"]).to eq({ "link" => "setting", "a" => 1, "b" => 2, "c" => 3 })
            binding_setting = json["@graph"].find { |setting| setting["@type"] == "ToolProxyBinding" }
            expect(binding_setting["custom"]).to eq({ "binding" => "setting", "a" => 1, "b" => 2, "d" => 4 })
            proxy_setting = json["@graph"].find { |setting| setting["@type"] == "ToolProxy" }
            expect(proxy_setting["custom"]).to eq({ "proxy" => "setting", "a" => 1, "c" => 3, "d" => 4 })
          end

          it 'finds the tool setting by "resource_link_id"' do
            resource_link_id = SecureRandom.uuid
            custom = { test: "value" }.with_indifferent_access
            Lti::ToolSetting.create!(
              tool_proxy:,
              resource_link_id:,
              custom:,
              context: tool_proxy.context
            )
            get "/api/lti/tool_proxy/#{tool_proxy.guid}/accounts/#{tool_proxy.context.id}/resource_link_id/#{resource_link_id}/tool_setting",
                headers: { "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings.simple+json", "Authorization" => "bearer #{access_token}" }
            response_body = JSON.parse(response.body)
            expect(response_body).to eq custom
          end
        end

        context "binding" do
          it "returns the simple json" do
            get "/api/lti/tool_settings/#{@binding_setting.id}.json",
                params: { tool_setting_id: @binding_setting.id },
                headers: { "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings.simple+json", "Authorization" => "oauth_token" }
            expect(JSON.parse(body)).to eq({ "binding" => "setting", "a" => 1, "b" => 2, "d" => 4 })
          end

          it "returns the lti_link using resource_link_id" do
            get "/api/lti/tool_proxy/#{tool_proxy.guid}/accounts/#{account.id}/tool_setting.json",
                params: {
                  tool_proxy_guid: tool_proxy.guid,
                  context_id: account.id
                },
                headers: { "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings.simple+json", "Authorization" => "oauth_token" }
            expect(JSON.parse(body)).to eq({ "binding" => "setting", "a" => 1, "b" => 2, "d" => 4 })
          end

          it "returns the tool settings json with bubble distinct" do
            get "/api/lti/tool_settings/#{@binding_setting.id}.json",
                params: { tool_setting_id: @link_setting, bubble: "distinct" },
                headers: { "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings+json", "Authorization" => "oauth_token" }
            json = JSON.parse(body)
            link_setting = json["@graph"].find { |setting| setting["@type"] == "LtiLink" }
            expect(link_setting).to be_nil
            binding_setting = json["@graph"].find { |setting| setting["@type"] == "ToolProxyBinding" }
            expect(binding_setting["custom"]).to eq({ "binding" => "setting", "a" => 1, "b" => 2, "d" => 4 })
            proxy_setting = json["@graph"].find { |setting| setting["@type"] == "ToolProxy" }
            expect(proxy_setting["custom"]).to eq({ "proxy" => "setting", "c" => 3 })
          end

          it "returns the tool settings simple json with bubble distinct" do
            get "/api/lti/tool_settings/#{@binding_setting.id}.json",
                params: { tool_setting_id: @link_setting, bubble: "distinct" },
                headers: { "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings.simple+json", "Authorization" => "oauth_token" }
            expect(JSON.parse(body)).to eq({ "binding" => "setting", "proxy" => "setting", "a" => 1, "b" => 2, "c" => 3, "d" => 4 })
          end

          it "bubbles up from binding" do
            get "/api/lti/tool_settings/#{@binding_setting.id}.json",
                params: { tool_setting_id: @binding_setting.id, bubble: "all" },
                headers: { "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings+json", "Authorization" => "oauth_token" }
            json = JSON.parse(body)
            link_setting = json["@graph"].find { |setting| setting["@type"] == "LtiLink" }
            expect(link_setting).to be_nil
            binding_setting = json["@graph"].find { |setting| setting["@type"] == "ToolProxyBinding" }
            expect(binding_setting["custom"]).to eq({ "binding" => "setting", "a" => 1, "b" => 2, "d" => 4 })
            proxy_setting = json["@graph"].find { |setting| setting["@type"] == "ToolProxy" }
            expect(proxy_setting["custom"]).to eq({ "proxy" => "setting", "a" => 1, "c" => 3, "d" => 4 })
          end
        end

        context "tool proxy" do
          it "returns the lti link simple json" do
            get "/api/lti/tool_settings/#{@proxy_setting.id}.json",
                params: { link_id: @proxy_setting.id },
                headers: { "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings.simple+json", "Authorization" => "oauth_token" }
            expect(JSON.parse(body)).to eq({ "proxy" => "setting", "a" => 1, "c" => 3, "d" => 4 })
          end

          it "returns the lti_link using resource_link_id" do
            get "/api/lti/tool_proxy/#{tool_proxy.guid}/tool_setting.json",
                params: {
                  tool_proxy_guid: tool_proxy.guid
                },
                headers: { "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings.simple+json", "Authorization" => "oauth_token" }
            expect(JSON.parse(body)).to eq({ "proxy" => "setting", "a" => 1, "c" => 3, "d" => 4 })
          end

          it "returns the tool settings json with bubble distinct" do
            get "/api/lti/tool_settings/#{@proxy_setting.id}.json",
                params: { tool_setting_id: @link_setting, bubble: "distinct" },
                headers: { "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings+json", "Authorization" => "oauth_token" }
            json = JSON.parse(body)
            link_setting = json["@graph"].find { |setting| setting["@type"] == "LtiLink" }
            expect(link_setting).to be_nil
            binding_setting = json["@graph"].find { |setting| setting["@type"] == "ToolProxyBinding" }
            expect(binding_setting).to be_nil
            proxy_setting = json["@graph"].find { |setting| setting["@type"] == "ToolProxy" }
            expect(proxy_setting["custom"]).to eq({ "proxy" => "setting", "a" => 1, "c" => 3, "d" => 4 })
          end

          it "returns the tool settings simple json with bubble distinct" do
            get "/api/lti/tool_settings/#{@proxy_setting.id}.json",
                params: { tool_setting_id: @link_setting, bubble: "distinct" },
                headers: { "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings.simple+json", "Authorization" => "oauth_token" }
            expect(JSON.parse(body)).to eq({ "proxy" => "setting", "a" => 1, "c" => 3, "d" => 4 })
          end

          it "bubbles up from tool proxy" do
            get "/api/lti/tool_settings/#{@proxy_setting.id}.json",
                params: { tool_setting_id: @proxy_setting.id, bubble: "all" },
                headers: { "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings+json", "Authorization" => "oauth_token" }
            json = JSON.parse(body)
            link_setting = json["@graph"].find { |setting| setting["@type"] == "LtiLink" }
            expect(link_setting).to be_nil
            binding_setting = json["@graph"].find { |setting| setting["@type"] == "ToolProxyBinding" }
            expect(binding_setting).to be_nil
            proxy_setting = json["@graph"].find { |setting| setting["@type"] == "ToolProxy" }
            expect(proxy_setting["custom"]).to eq({ "proxy" => "setting", "a" => 1, "c" => 3, "d" => 4 })
          end
        end
      end

      describe "#update" do
        it "returns as a bad request when bubble is set" do
          tool_setting = ToolSetting.create(tool_proxy:, context: account, resource_link_id: "resource_link")
          params = { "link" => "settings" }
          put "/api/lti/tool_settings/#{tool_setting.id}.json?bubble=all",
              params: params.to_json,
              headers: { "CONTENT_TYPE" => "application/vnd.ims.lti.v2.toolsettings.simple+json",
                         "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings.simple+json",
                         "Authorization" => "oauth_token" }
          expect(response).to have_http_status :bad_request
        end

        it "returns as a bad request when there is more than one @graph item" do
          tool_setting = ToolSetting.create(tool_proxy:, context: account, resource_link_id: "resource_link")
          params = {
            "@context" => "http://purl.imsglobal.org/ctx/lti/v2/ToolSettings",
            "@graph" => [
              {
                "@type" => "LtiLink",
                "@id" => "http://sample.invalid/api/lti/tool_settings/#{tool_setting.id}",
                "custom" => { "link" => "settings" }
              },
              {
                "@type" => "ToolProxyBinding",
                "@id" => "http://sample.invalid/api/lti/tool_settings/#{tool_setting.id + 1}",
                "custom" => { "binding" => "settings" }
              }
            ]
          }
          put "/api/lti/tool_settings/#{tool_setting.id}.json",
              params: params.to_json,
              headers: { "CONTENT_TYPE" => "application/vnd.ims.lti.v2.toolsettings+json",
                         "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings.simple+json",
                         "Authorization" => "oauth_token" }
          expect(response).to have_http_status :bad_request
        end

        it "updates a tool_setting with a single graph element" do
          tool_setting = ToolSetting.create(tool_proxy:, context: account, resource_link_id: "resource_link")
          params = {
            "@context" => "http://purl.imsglobal.org/ctx/lti/v2/ToolSettings",
            "@graph" => [
              {
                "@type" => "LtiLink",
                "@id" => "http://sample.invalid/api/lti/tool_settings/#{tool_setting.id}",
                "custom" => { "link" => "settings" }
              }
            ]
          }
          put "/api/lti/tool_settings/#{tool_setting.id}.json",
              params: params.to_json,
              headers: { "CONTENT_TYPE" => "application/vnd.ims.lti.v2.toolsettings+json",
                         "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings.simple+json",
                         "Authorization" => "oauth_token" }
          expect(tool_setting.reload.custom).to eq({ "link" => "settings" })
        end

        context "lti_link" do
          it "creates a new lti link tool setting" do
            tool_setting = ToolSetting.create(tool_proxy:, context: account, resource_link_id: "resource_link")
            params = { "link" => "settings" }
            put "/api/lti/tool_settings/#{tool_setting.id}.json",
                params: params.to_json,
                headers: { "CONTENT_TYPE" => "application/vnd.ims.lti.v2.toolsettings.simple+json",
                           "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings.simple+json",
                           "Authorization" => "oauth_token" }
            expect(tool_setting.reload.custom).to eq({ "link" => "settings" })
          end
        end

        context "binding" do
          it "creates a new binding tool setting" do
            tool_setting = ToolSetting.create(tool_proxy:, context: account)
            params = { "binding" => "settings" }
            put "/api/lti/tool_settings/#{tool_setting.id}.json",
                params: params.to_json,
                headers: { "CONTENT_TYPE" => "application/vnd.ims.lti.v2.toolsettings.simple+json",
                           "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings.simple+json",
                           "Authorization" => "oauth_token" }
            expect(tool_setting.reload.custom).to eq({ "binding" => "settings" })
          end
        end

        context "proxy" do
          it "creates a new tool_proxy tool setting" do
            tool_setting = ToolSetting.create(tool_proxy:)
            params = { "tool_proxy" => "settings" }
            put "/api/lti/tool_settings/#{tool_setting.id}.json",
                params: params.to_json,
                headers: { "CONTENT_TYPE" => "application/vnd.ims.lti.v2.toolsettings.simple+json",
                           "HTTP_ACCEPT" => "application/vnd.ims.lti.v2.toolsettings.simple+json",
                           "Authorization" => "oauth_token" }
            expect(tool_setting.reload.custom).to eq({ "tool_proxy" => "settings" })
          end
        end
      end
    end
  end
end
