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

# Manually stubbing the actual API request
describe AppCenter::AppApi do
  let(:api) { AppCenter::AppApi.new(@account) }

  before do
    account_model
    default_settings = api.app_center.default_settings
    default_settings["base_url"] = "http://www.example.com"
    default_settings["apps_index_endpoint"] = "/apps"
    default_settings["token"] = "ABCDEFG1234567"
    PluginSetting.create!(name: api.app_center.id, settings: default_settings)
  end

  it "finds the app_center plugin" do
    expect(api.app_center).not_to be_nil
    expect(api.app_center).to eq Canvas::Plugin.find(:app_center)
    expect(api.app_center).to be_enabled
    expect(api.app_center.settings["base_url"]).not_to be_empty
  end

  describe "#fetch_app_center_response" do
    let(:response) do
      response = double
      allow(response).to receive(:body).and_return(
        {
          "meta" => { "next" => "https://www.example.com/api/v1/apps?offset=60" },
          "current_offset" => 0,
          "limit" => 50,
          "objects" => %w[object1 object2 object3 object4]
        }.to_json
      )
      response
    end

    it "can handle query params in the endpoint" do
      endpoint = "/?myparam=value"
      per_page = 11
      page = 3
      expect(CanvasHttp).to receive(:get).with("#{api.app_center.settings["base_url"]}#{endpoint}&offset=#{(page * per_page) - per_page}").and_return(response)
      api.fetch_app_center_response(endpoint, 11.minutes, page, per_page)
    end

    it "can handle an invalid response" do
      allow(response).to receive(:body).and_return("")
      expect(CanvasHttp).to receive(:get).and_return(response)
      expect(api.fetch_app_center_response("", 12.minutes, 8, 3)).to eq({})
    end

    it "can handle an error response" do
      message = { "message" => "Tool not found", "type" => "error" }
      allow(response).to receive(:body).and_return(message.to_json)
      expect(CanvasHttp).to receive(:get).and_return(response)
      expect(api.fetch_app_center_response("", 13.minutes, 6, 9)).to eq message
    end

    it "respects per_page param" do
      endpoint = "/?myparam=value"
      per_page = 1
      page = 1
      offset = (page * per_page) - per_page
      expect(CanvasHttp).to receive(:get).with("#{api.app_center.settings["base_url"]}#{endpoint}&offset=#{offset}").and_return(response)
      response = api.fetch_app_center_response(endpoint, 11.minutes, page, per_page)
      results = response["objects"]
      expect(results.size).to eq 1
      expect(results.first).to eq "object1"
      expect(response["meta"]["next_page"]).to eq 2
    end

    it "can omit next page" do
      message = { "objects" => %w[object1 object2 object3 object4], "meta" => {} }
      allow(response).to receive(:body).and_return(message.to_json)
      endpoint = "/?myparam=value"
      per_page = 5
      page = 1
      offset = (page * per_page) - per_page
      expect(CanvasHttp).to receive(:get).with("#{api.app_center.settings["base_url"]}#{endpoint}&offset=#{offset}").and_return(response)
      response = api.fetch_app_center_response(endpoint, 11.minutes, page, per_page)
      results = response["objects"]
      expect(results.size).to eq 4
      expect(response["meta"]["next_page"]).to be_nil
    end

    describe "caching" do
      it "resets the cache when getting an invalid response" do
        enable_cache do
          allow(response).to receive(:body).and_return("")
          expect(CanvasHttp).to receive(:get).and_return(response).twice
          expect(api.fetch_app_center_response("", 13.minutes, 7, 4)).to eq({})
          expect(api.fetch_app_center_response("", 13.minutes, 7, 4)).to eq({})
        end
      end

      it "uses the configured token as part of the cache key" do
        enable_cache do
          expect(CanvasHttp).to receive(:get).and_return(response).twice

          api.fetch_app_center_response("/endpoint/url", 13.minutes, 7, 4)

          plugin_setting = PluginSetting.where(name: api.app_center.id).first
          plugin_setting.settings["token"] = "new_token"
          plugin_setting.save!

          api2 = AppCenter::AppApi.new(@account)

          api2.fetch_app_center_response("/endpoint/url", 13.minutes, 7, 4)
        end
      end
    end
  end

  describe "#get_app_config_url" do
    let(:app_center_response) do
      {
        "id" => 163,
        "short_name" => "pr_youtube",
        "name" => "YouTube",
        "description" => "\n<p>Search publicly available YouTube videos.</p>\n",
        "short_description" => "Search publicly available YouTube videos.",
        "status" => "active",
        "app_type" => nil,
        "preview_url" => "https://www.edu-apps.org/lti_public_resources/?tool_id=youtube",
        "banner_image_url" => "https://edu-app-center.s3.amazonaws.com/uploads/pr_youtube.png",
        "logo_image_url" => nil,
        "icon_image_url" => nil,
        "average_rating" => 4.0,
        "total_ratings" => 5.0,
        "is_certified" => false,
        "config_xml_url" => "https://www.edu-apps.org/lti_public_resources/config.xml?id=youtube",
        "requires_secret" => false,
        "config_options" => [
          {
            "name" => "channel_name",
            "param_type" => "text",
            "default_value" => "",
            "description" => "Channel Name (Optional)",
            "is_required" => false
          }
        ]
      }
    end
    let(:app_center_id) { "pr_youtube" }
    let(:config_settings) { { custom_param: "custom_value" } }
    let(:sub_account) { Account.create!(parent_account: @account, name: "sub_account") }
    let(:sub_api) { AppCenter::AppApi.new(sub_account) }
    let(:get_app_config_url) { api.get_app_config_url(app_center_id, config_settings) }
    let(:get_sub_app_config_url) { sub_api.get_app_config_url(app_center_id, config_settings) }

    it "gets the details of the specified app" do
      config_settings = {}

      allow(api).to receive(:fetch_app_center_response).and_return(app_center_response)

      url = api.get_app_config_url(app_center_id, config_settings)
      expect(url).to eq app_center_response["config_xml_url"]
    end

    it "appends config settings to an existing query string" do
      allow(api).to receive(:fetch_app_center_response).and_return(app_center_response)

      expect(get_app_config_url).to eq "#{app_center_response["config_xml_url"]}&custom_param=custom_value"
    end

    it "creates a query string populated with config settings" do
      app_center_response["config_xml_url"] = "https://www.edu-apps.org/lti_public_resources/config.xml"
      allow(api).to receive(:fetch_app_center_response).and_return(app_center_response)

      expect(get_app_config_url).to eq "#{app_center_response["config_xml_url"]}?custom_param=custom_value"
    end

    it "returns nil if app center id is invalid" do
      app_center_response = ""

      allow(api).to receive(:fetch_app_center_response).and_return(app_center_response)

      expect(get_app_config_url).to be_nil
    end

    it "sends the app center token" do
      endpoint = "/api/v1/lti_apps/pr_youtube?access_token=#{api.app_center.settings["token"]}"

      allow(api).to receive(:fetch_app_center_response).with(endpoint, 300, 1, 1).and_return(app_center_response)

      expect(get_app_config_url).to eq "#{app_center_response["config_xml_url"]}&custom_param=custom_value"
    end

    it "uses the current context app center token if set" do
      @account.settings[:app_center_access_token] = "account_token"
      endpoint = "/api/v1/lti_apps/pr_youtube?access_token=account_token"

      expect(api).to receive(:fetch_app_center_response).with(endpoint, 5.minutes, 1, 1).and_return(app_center_response)
      get_app_config_url
    end

    it "uses the inherited access token if not set at current context" do
      sub_account.settings[:app_center_access_token] = nil
      @account.settings[:app_center_access_token] = "root_account_token"
      endpoint = "/api/v1/lti_apps/pr_youtube?access_token=root_account_token"

      allow_any_instance_of(Account).to receive(:calculate_inherited_setting).and_return({ locked: false, value: "root_account_token" })

      expect(sub_api).to receive(:fetch_app_center_response).with(endpoint, 5.minutes, 1, 1).and_return(app_center_response)
      get_sub_app_config_url
    end

    it "defaults to the plugin setting access token if no current context token is set" do
      sub_account.settings[:app_center_access_token] = nil
      @account.settings[:app_center_access_token] = nil
      endpoint = "/api/v1/lti_apps/pr_youtube?access_token=ABCDEFG1234567"

      expect(sub_api).to receive(:fetch_app_center_response).with(endpoint, 5.minutes, 1, 1).and_return(app_center_response)
      get_sub_app_config_url
    end
  end

  describe "#get_apps" do
    let(:response) do
      response = double
      allow(response).to receive(:body).and_return(
        {
          "meta" => { "next" => "https://www.example.com/api/v1/apps?offset=72" },
          "current_offset" => 0,
          "limit" => 72,
          "objects" => [
            {
              "name" => "First Tool",
              "id" => "first_tool",
            },
            {
              "name" => "Second Tool",
              "id" => "second_tool",
            }
          ]
        }.to_json
      )
      response
    end

    it "gets a list of apps" do
      allow(CanvasHttp).to receive(:get).and_return(response)
      apps = api.get_apps["lti_apps"]
      expect(apps).to be_a Array
      expect(apps.size).to eq 2
    end

    it "returns an empty hash if the app center is disabled" do
      allow(CanvasHttp).to receive(:get).and_return(response)
      setting = PluginSetting.find_by_name(api.app_center.id)
      setting.destroy

      expect(api.app_center).not_to be_enabled

      response = api.get_apps
      expect(response).to be_a Hash
      expect(response.size).to eq 0
    end

    it "gets the next page" do
      enable_cache do
        allow(CanvasHttp).to receive(:get).and_return(response)
        response = api.get_apps
        expect(response["meta"]["next_page"]).to eq 2
      end
    end

    it "caches apps results" do
      enable_cache do
        expect(CanvasHttp).to receive(:get).and_return(response).once
        api.get_apps
        api.get_apps
      end
    end

    it "caches multiple calls" do
      enable_cache do
        expect(CanvasHttp).to receive(:get).and_return(response).exactly(2).times
        api.get_apps(0)
        api.get_apps(1)
        api.get_apps(0)
        api.get_apps(1)
      end
    end

    it "can handle an edu-apps api v1 response" do
      app = {
        "name" => "Wikipedia",
        "id" => "wikipedia",
        "categories" => [
          "Completely Free",
          "Open Content",
          "Web 2.0"
        ],
        "levels" => [
          "K-6th Grade",
          "7th-12th Grade",
          "Postsecondary"
        ],
        "description" => "Search through English Wikipedia articles and link to or embed these articles into course material.",
        "app_type" => "open_launch",
        "short_description" => "Articles from The Free Encyclopedia",
        "extensions" => [
          "editor_button",
          "resource_selection"
        ],
        "beta" => false,
        "test_instructions" => "",
        "support_link" => "https://twitter.com/whitmer",
        "ims_link" => "",
        "author_name" => "Brian Whitmer",
        "privacy_level" => "anonymous",
        "width" => 590,
        "height" => 450,
        "added" => "2012-03-22T00:00:00Z",
        "uses" => 30,
        "submitter_name" => nil,
        "submitter_url" => nil,
        "ratings_count" => 1,
        "comments_count" => 1,
        "avg_rating" => 5,
        "banner_url" => "http://mosdef.instructure.com/tools/wikipedia/banner.png",
        "logo_url" => "http://mosdef.instructure.com/tools/wikipedia/logo.png",
        "icon_url" => "http://mosdef.instructure.com/tools/wikipedia/icon.png",
        "new" => false,
        "config_url" => "http://mosdef.instructure.com/tools/wikipedia/config.xml",
        "any_key" => true,
        "preview" => {
          "url" => "/tools/wikipedia/index.html",
          "height" => 450
        },
        "short_name" => "wikipedia",
        "banner_image_url" => "http://mosdef.instructure.com/tools/wikipedia/banner.png",
        "logo_image_url" => "http://mosdef.instructure.com/tools/wikipedia/logo.png",
        "icon_image_url" => "http://mosdef.instructure.com/tools/wikipedia/icon.png",
        "config_xml_url" => "http://mosdef.instructure.com/tools/wikipedia/config.xml",
        "average_rating" => 5,
        "total_ratings" => 1,
        "config_options" => [
          {
            "name" => "launch_url",
            "description" => "Launch URL",
            "type" => "text",
            "value" => "example.com",
            "required" => true
          }
        ]
      }

      allow(response).to receive(:body).and_return({ "objects" => [app] }.to_json)
      expect(CanvasHttp).to receive(:get).and_return(response)
      json = api.get_apps(0)
      tool = json["lti_apps"].first
      expect(tool["short_name"]).to eq app["id"]
      expect(tool["banner_image_url"]).to eq app["banner_url"]
      expect(tool["logo_image_url"]).to eq app["logo_url"]
      expect(tool["icon_image_url"]).to eq app["icon_url"]
      expect(tool["config_xml_url"]).to eq app["config_url"]
      expect(tool["average_rating"]).to eq app["avg_rating"]
      expect(tool["total_ratings"]).to eq app["ratings_count"]
      expect(tool["requires_secret"]).to eq !app["any_key"]
      opt = tool["config_options"].first
      expect(opt["name"]).to eq app["config_options"].first["name"]
      expect(opt["description"]).to eq app["config_options"].first["description"]
      expect(opt["param_type"]).to eq app["config_options"].first["type"]
      expect(opt["value"]).to eq app["config_options"].first["value"]
      expect(opt["is_required"]).to eq app["config_options"].first["required"]
    end

    it "can handle an edu-apps api v2 response" do
      app = {
        "id" => 2,
        "short_name" => "public_collections",
        "name" => "Public Collections",
        "short_description" => "",
        "status" => "active",
        "is_public" => true,
        "app_type" => "open_launch",
        "preview_url" => "http://www.edu-apps.org/tools/public_collections/index.html",
        "banner_image_url" => "http://www.edu-apps.org/tools/public_collections/banner.png",
        "logo_image_url" => "http://www.edu-apps.org/tools/public_collections/logo.png",
        "icon_image_url" => nil,
        "average_rating" => 0,
        "total_ratings" => 0,
        "is_certified" => false,
        "config_xml_url" => "http://localhost:3001/configurations/b1us84b2fewp5gqr.xml",
        "requires_secret" => true,
        "tags" => [
          {
            "id" => 11,
            "short_name" => "media",
            "name" => "Media",
            "context" => "category"
          },
          {
            "id" => 12,
            "short_name" => "open_content",
            "name" => "Open Content",
            "context" => "category"
          },
          {
            "id" => 16,
            "short_name" => "web_20",
            "name" => "Web 2.0",
            "context" => "category"
          },
          {
            "id" => 17,
            "short_name" => "free",
            "name" => "Completely Free",
            "context" => "category"
          },
          {
            "id" => 19,
            "short_name" => "K-6",
            "name" => "K-6th Grade",
            "context" => "education_level"
          },
          {
            "id" => 20,
            "short_name" => "7-12",
            "name" => "7th-12th Grade",
            "context" => "education_level"
          },
          {
            "id" => 21,
            "short_name" => "postsecondary",
            "name" => "Postsecondary",
            "context" => "education_level"
          }
        ]
      }
      allow(response).to receive(:body).and_return({ "lti_apps" => [app] }.to_json)
      expect(CanvasHttp).to receive(:get).and_return(response)
      json = api.get_apps(0)
      tool = json["lti_apps"].first

      expect(tool["categories"].length).to eq 4
      expect(tool["extensions"]).to be_nil
      expect(tool["levels"].length).to eq 3
    end
  end
end
