require "rails_helper"

RSpec.describe "UserSettings" do
  let(:user) { create(:user) }

  describe "GET /settings/:tab" do
    context "when not signed-in" do
      it "redirects them to login" do
        get "/settings"
        expect(response).to redirect_to("/enter")
      end
    end

    context "when signed-in" do
      before { sign_in user }

      it "renders various settings tabs properly" do
        Constants::Settings::TAB_LIST.each do |tab|
          get user_settings_path(tab.downcase.tr(" ", "-"))

          expect(response.body).to include("@#{user.username}")
        end
      end

      it "handles unknown settings tab properly" do
        expect { get "/settings/does-not-exist" }
          .to raise_error(ActiveRecord::RecordNotFound)
      end

      it "displays content on Profile tab properly" do
        get user_settings_path(:profile)

        expect(response.body).to include("User")
      end

      it "displays profile groups content on Profile tab" do
        profile_field = create(:profile_field)

        get user_settings_path(:profile)

        expect(response.body).to include(profile_field.profile_field_group.name)
      end

      it "displays content on Customization tab properly" do
        get user_settings_path(:customization)

        expect(response.body).to include("Appearance", "Writing", "Content", "Sponsors", "Announcements", "Content Relocation")
      end

      it "displays content on Notifications tab properly" do
        get user_settings_path(:notifications)

        expect(response.body).to include("Email notifications", "Mobile notifications", "General notifications")
      end

      it "displays moderator notifications second on Notifications tab if trusted" do
        user.add_role(:trusted)

        get user_settings_path(:notifications)

        expect(response.body).to include("Moderator notifications")
      end

      it "displays content on Account tab properly" do
        get user_settings_path(:account)

        expect(response.body).to include("Set new password", "Account emails", "Danger Zone")
      end

      it "displays content on Billing tab properly" do
        get user_settings_path(:billing)

        expect(response.body).to include("Billing")
      end

      it "displays content on Organization tab properly" do
        get user_settings_path(:organization)

        expect(response.body).to include("Join An Organization", "Create An Organization")
      end

      it "displays content on Extensions tab properly" do
        get user_settings_path(:extensions)

        feed_section = "Publishing to #{Settings::Community.community_name} from RSS"
        titles = ["Comment templates", feed_section, "API Keys"]
        expect(response.body).to include(*titles)
      end

      it "includes contact us on RSS page properly" do
        get user_settings_path(:extensions)
        expect(response.body).to include(I18n.t("contact_prompts.if_any_questions_html"))
      end

      it "renders heads up dupe account message with proper param" do
        get "/settings?state=previous-registration"

        error_message = "There is an existing account authorized with that social account"
        expect(response.body).to include(error_message)
      end

      it "renders the proper response template" do
        response_template = create(:response_template, user: user)

        get user_settings_path(tab: "response-templates", id: response_template.id)

        expect(response.body).to include("Edit comment template")
      end
    end

    describe ":account" do
      let(:remove_oauth_section) { "Remove OAuth Associations" }
      let(:remove_oauth_description) { "You can remove one of your authentication methods" }
      let(:remove_oauth_instructions) { "Please add another authentication method" }
      let(:user) { create(:user, :with_identity) }

      before do
        omniauth_mock_providers_payload
        sign_in user
      end

      it "allows users to visit the account page" do
        get user_settings_path(tab: "account")
        expect(response).to have_http_status(:ok)
      end

      it "shows the description if a user has multiple enabled identities" do
        allow(Authentication::Providers).to receive(:enabled).and_return(Authentication::Providers.available)
        providers = Authentication::Providers.available.first(2)
        allow(user).to receive(:identities).and_return(user.identities.where(provider: providers))

        get user_settings_path(tab: "account")
        expect(response.body).to include(remove_oauth_description)
        expect(response.body).not_to include(remove_oauth_instructions)
      end

      it "shows instructions how to remove an identity if a user has one enabled identity" do
        provider = Authentication::Providers.available.first
        allow(Authentication::Providers).to receive(:enabled).and_return([provider])
        allow(user).to receive(:identities).and_return(user.identities.where(provider: provider))

        get user_settings_path(tab: "account")
        expect(response.body).not_to include(remove_oauth_description)
        expect(response.body).to include(remove_oauth_instructions)
      end

      it "shows instructions how to remove an identity if a user has one enabled identity and one disabled" do
        provider = Authentication::Providers.available.first
        allow(Authentication::Providers).to receive(:enabled).and_return([provider])

        get user_settings_path(tab: "account")
        expect(response.body).not_to include(remove_oauth_description)
        expect(response.body).to include(remove_oauth_instructions)
      end

      it "hides the 'Remove OAuth' section if a user has no enabled identity" do
        allow(Authentication::Providers).to receive(:enabled).and_return([])

        get user_settings_path(tab: "account")
        expect(response.body).not_to include(remove_oauth_section)
      end
    end

    describe "connect providers accounts" do
      before do
        omniauth_mock_providers_payload
      end

      it "does not render the text for the enabled provider the user has an identity for" do
        allow(Authentication::Providers).to receive(:enabled).and_return(Authentication::Providers.available)
        user = create(:user, :with_identity, identities: [:github])

        sign_in user
        get user_settings_path

        expect(response.body).not_to include("Connect GitHub Account")
      end

      it "does not render the text for the disabled provider the user has an identity for" do
        providers = Authentication::Providers.available - %i[github]
        allow(Authentication::Providers).to receive(:enabled).and_return(providers)
        user = create(:user, :with_identity, identities: [:github])

        sign_in user
        get user_settings_path

        expect(response.body).not_to include("Connect GitHub Account")
      end

      it "renders the text for the enabled provider the user has no identity for" do
        allow(Authentication::Providers).to receive(:enabled).and_return(Authentication::Providers.available)
        user = create(:user, :with_identity, identities: [:twitter])

        sign_in user
        get user_settings_path

        expect(response.body).to include("Connect GitHub Account")
      end

      it "does not allow to connect an Apple Account to an existing user" do
        allow(Authentication::Providers).to receive(:enabled).and_return(Authentication::Providers.available)
        user = create(:user, :with_identity, identities: [:twitter])

        sign_in user
        get user_settings_path

        expect(response.body).to include("Connect GitHub Account")
        expect(response.body).not_to include("Connect Apple Account")
      end
    end

    describe "GitHub repositories" do
      it "renders the repositories container if the user has authenticated through GitHub" do
        allow(Authentication::Providers).to receive(:enabled).and_return(Authentication::Providers.available)
        user = create(:user, :with_identity, identities: [:github])

        sign_in user
        get user_settings_path(:extensions)

        expect(response.body).to include("github-repos-container")
      end

      it "does not render anything if the user has not authenticated through GitHub" do
        sign_in user
        get user_settings_path(:extensions)

        expect(response.body).not_to include("github-repos-container")
      end
    end
  end

  describe "GET /settings/profile" do
    before { sign_in user }

    context "when user has profile image" do
      it "displays profile image upload input" do
        get user_settings_path(:profile)

        expect(response.body).to include("user[profile_image]")
      end
    end

    context "when user does not have a profile image" do
      let(:user) { create(:user, profile_image: nil) }

      it "displays profile image upload input" do
        get user_settings_path(:profile)

        expect(response.body).to include("user[profile_image]")
      end
    end
  end

  describe "PUT /update/:id" do
    before { sign_in user }

    it "updates profile_updated_at" do
      user.update_column(:profile_updated_at, 2.weeks.ago)
      put "/users/#{user.id}", params: { user: { tab: "profile", username: "new_username" } }
      expect(user.reload.profile_updated_at).to be > 2.minutes.ago
    end

    it "updates the users announcement display preferences (in both users and user_settings tables)" do
      expect(user.setting.display_announcements).to be(true)

      expect do
        put users_settings_path(user.setting.id), params: { users_setting: { display_announcements: 0 } }
      end.to change { user.setting.reload.display_announcements }.from(true).to(false)

      expect(user.setting.reload.display_announcements).to be(false)
    end

    it "updates the users subforem reassignment preferences" do
      expect(user.setting.disallow_subforem_reassignment).to be(false)

      expect do
        put users_settings_path(user.setting.id), params: { users_setting: { disallow_subforem_reassignment: 1 } }
      end.to change { user.setting.reload.disallow_subforem_reassignment }.from(false).to(true)

      expect(user.setting.reload.disallow_subforem_reassignment).to be(true)
    end

    it "allows users to enable subforem reassignment" do
      user.setting.update!(disallow_subforem_reassignment: true)

      expect do
        put users_settings_path(user.setting.id), params: { users_setting: { disallow_subforem_reassignment: 0 } }
      end.to change { user.setting.reload.disallow_subforem_reassignment }.from(true).to(false)

      expect(user.setting.reload.disallow_subforem_reassignment).to be(false)
    end

    it "displays the subforem reassignment setting in the customization form" do
      get user_settings_path(:customization)

      expect(response.body).to include("disallow_subforem_reassignment")
      expect(response.body).to include("Disable automatic relocation of my content to other subforems")
    end

    it "shows the correct checkbox state for subforem reassignment setting" do
      user.setting.update!(disallow_subforem_reassignment: true)

      get user_settings_path(:customization)

      expect(response.body).to include('checked="checked"')
    end

    it "updates username to too short username" do
      put "/users/#{user.id}", params: { user: { tab: "profile", username: "h" } }
      expect(response.body).to include("Username is too short")
    end

    it "returns error message if user can't be saved" do
      put "/users/#{user.id}", params: { user: { password: "1", password_confirmation: "1" } }

      expect(flash[:error]).to include("Password is too short")
    end

    it "returns an error message if the passwords do not match" do
      put "/users/#{user.id}", params: { user: { password: "asdfghjk", password_confirmation: "qwertyui" } }

      expect(flash[:error]).to include("Password doesn't match password confirmation")
    end

    context "when requesting an export of the articles" do
      def send_request(export_requested: true)
        put user_path(user.id), params: { user: { tab: :account, export_requested: export_requested } }
      end

      it "updates export_requested flag" do
        send_request
        expect(user.reload.export_requested).to be(true)
      end

      it "displays a flash with a reminder for the user to expect an email" do
        send_request
        expect(flash[:settings_notice]).to include("The export will be emailed to you shortly.")
      end

      it "hides the checkbox" do
        send_request
        follow_redirect!
        expect(response.body).not_to include("Request an export of your posts")
      end

      it "tells the user they recently requested an export" do
        send_request
        follow_redirect!
        expect(response.body).to include("You have recently requested an export")
      end

      it "sends an email" do
        allow(ForemInstance).to receive(:smtp_enabled?).and_return(true)
        expect do
          sidekiq_perform_enqueued_jobs do
            send_request
          end
        end.to change { ActionMailer::Base.deliveries.count }.by(1)
      end

      it "does not send an email if there was no request" do
        sidekiq_perform_enqueued_jobs do
          expect { send_request(export_requested: false) }.not_to(change { ActionMailer::Base.deliveries.count })
        end
      end
    end

    context "when requesting a fetch of the feed", vcr: { cassette_name: "feeds_import_medium_vaidehi" } do
      let(:feed_url) { "https://medium.com/feed/@vaidehijoshi" }
      let(:user) do
        u = create(:user)
        u.setting.update(feed_url: feed_url)
        u
      end

      it "invokes Feeds::ImportArticlesWorker" do
        allow(Feeds::ImportArticlesWorker).to receive(:perform_async).with(user.id)

        put users_settings_path(user.setting.id), params: { users_setting: { feed_url: feed_url } }

        expect(Feeds::ImportArticlesWorker).to have_received(:perform_async).with(user.id)
      end
    end
  end

  describe "DELETE /users/remove_identity" do
    let(:provider) { Authentication::Providers.available.first }

    context "when user has multiple identities" do
      let(:user) { create(:user, :with_identity) }

      before do
        omniauth_mock_providers_payload
        allow(Settings::Authentication).to receive(:providers).and_return(Authentication::Providers.available)
        sign_in user
      end

      it "removes the correct identity" do
        expect do
          delete users_remove_identity_path, params: { provider: provider }
        end.to change(user.identities, :count).by(-1)

        expect(user.identities.map(&:provider)).not_to include(provider)
      end

      it "empties their associated username" do
        delete users_remove_identity_path, params: { provider: provider }

        expect(user.public_send("#{provider}_username")).to be_nil
      end

      it "updates the profile_updated_at timestamp" do
        original_profile_updated_at = user.profile_updated_at
        delete users_remove_identity_path, params: { provider: provider }
        expect(user.profile_updated_at.to_i).to be > original_profile_updated_at.to_i
      end

      it "redirects successfully to /settings/account" do
        delete users_remove_identity_path, params: { provider: provider }
        expect(response).to redirect_to("/settings/account")
      end

      it "renders a successful response message" do
        delete users_remove_identity_path, params: { provider: provider }
        auth_provider = Authentication::Providers.get!(provider)

        expected_notice = "Your #{auth_provider.official_name} account was successfully removed."
        expect(flash[:settings_notice]).to eq(expected_notice)
      end

      it "redirects the user with an error if the corresponding provider has been since disabled" do
        providers = Authentication::Providers.available - [provider]
        allow(Authentication::Providers).to receive(:enabled).and_return(providers)
        delete users_remove_identity_path, params: { provider: provider }
        expect(response).to redirect_to("/settings/account")

        error =
          "An error occurred. Please try again or send an email to: #{ForemInstance.contact_email}"
        expect(flash[:error]).to eq(error)
      end

      it "does not show the 'Remove OAuth' section afterwards if only one identity remains" do
        providers = Authentication::Providers.available.first(2)
        allow(user).to receive(:identities).and_return(user.identities.where(provider: providers))

        delete users_remove_identity_path, params: { provider: providers.first }
        expect(response.body).not_to include("Remove OAuth Associations")
      end

      it "does not remove GitHub repositories if the removed identity is not GitHub" do
        create(:github_repo, user: user)

        expect do
          delete users_remove_identity_path, params: { provider: :twitter }
        end.not_to change(user.github_repos, :count)
      end

      it "removes GitHub repositories if the removed identity is GitHub" do
        repo = create(:github_repo, user: user)

        expect do
          delete users_remove_identity_path, params: { provider: :github }
        end.to change(user.github_repos, :count).by(-1)

        expect(GithubRepo.exists?(id: repo.id)).to be(false)
      end
    end

    # Users won't be able to do this via the view, but in case they hit the route somehow...
    context "when user has only one identity" do
      let(:user) { create(:user, :with_identity, identities: [provider]) }

      before do
        sign_in user
      end

      it "sets the proper flash error message" do
        delete users_remove_identity_path, params: { provider: provider }

        error =
          "An error occurred. Please try again or send an email to: #{ForemInstance.contact_email}"
        expect(flash[:error]).to eq(error)
      end

      it "does not delete any identities" do
        expect do
          delete users_remove_identity_path, params: { provider: provider }
        end.not_to change(user.identities, :count)
      end

      it "redirects successfully to the Settings-Account page" do
        delete users_remove_identity_path, params: { provider: provider }
        expect(response).to redirect_to(user_settings_path(:account))
      end
    end
  end
end
