require "rails_helper"

RSpec.describe "/admin/member_manager/users" do
  let!(:user) do
    omniauth_mock_github_payload
    create(:user, :with_identity, identities: ["github"])
  end
  let(:admin) { create(:user, :super_admin) }

  before do
    sign_in(admin)
  end

  describe "GET /admin/member_manager/users" do
    it "renders to appropriate page" do
      get admin_users_path
      expect(response.body).to include(user.username)
    end

    context "when searching" do
      it "finds the proper user by GitHub username" do
        get "#{admin_users_path}?search=#{user.github_username}"
        expect(response.body).to include(CGI.escapeHTML(user.github_username))
      end
    end

    context "when filtering by role" do
      it "filters and shows the proper user(s)" do
        get "#{admin_users_path}?search&role=super_admin"
        expect(response.body).to include(CGI.escapeHTML(admin.name))
      end
    end
  end

  describe "GET /admin/member_manager/users/:id" do
    it "renders to appropriate page" do
      get admin_user_path(user)

      expect(response.body).to include(user.username)
    end

    it "redirects from /username/moderate" do
      get "/#{user.username}/moderate"
      expect(response).to redirect_to(admin_user_path(user.id))
    end

    it "shows banish button for new users" do
      get admin_user_path(user.id)
      expect(response.body).to include("Banish user")
    end

    it "does not show banish button for non-admins" do
      sign_out(admin)
      expect { get admin_user_path(user.id) }.to raise_error(Pundit::NotAuthorizedError)
    end

    it "displays a user's current roles in the 'Overview' tab" do
      get admin_user_path(user.id)
      expect(response.body).to include("Roles")
    end

    it "displays a user's current roles in the 'Emails' tab" do
      get "#{admin_user_path(user.id)}?tab=emails"
      expect(response.body).to include("Previous emails")
    end

    it "displays a user's current flags in the 'Flags' tab" do
      get "#{admin_user_path(user.id)}?tab=flags"
      expect(response.body).to include("Flags received")
    end

    it "displays a message when there are no related vomit reactions for a user" do
      get "#{admin_user_path(user.id)}?tab=flags"
      expect(response.body).to include("No flags received against")
    end

    it "displays a list of recent related vomit reactions for a user if any exist" do
      vomit = build(:reaction, category: "vomit", user_id: user.id, reactable_type: "Article", status: "valid")
      get admin_user_path(user.id)
      expect(response.body).to include(vomit.reactable_type)
    end

    it "displays a user's current reports in the 'Reports' tab" do
      get "#{admin_user_path(user.id)}?tab=reports"
      expect(response.body).to include("Reports submitted by")
    end

    it "displays a message when there are no current reports for a user" do
      get "#{admin_user_path(user.id)}?tab=reports"
      expect(response.body).to include("No comment or post has been reported yet.")
    end

    it "displays a list of current reports for a user if any exist" do
      report = build(:feedback_message, category: "spam", affected_id: user.id, feedback_type: "spam", status: "Open")
      get admin_user_path(user.id)
      expect(response.body).to include(report.feedback_type)
    end

    it "displays unpublish all data from logs when it exists on unpublish_alls tab" do
      article = create(:article, user: user, published: false)
      create(:audit_log, user: admin, slug: "unpublish_all_articles",
                         data: { target_article_ids: [article.id], target_user_id: user.id })
      get "#{admin_user_path(user.id)}?tab=unpublish_logs"
      expect(response.body).to include("Unpublished by")
      expect(response.body).to include(CGI.escapeHTML(article.title))
    end

    it "displays a label if an unpublished post was republished" do
      article = create(:article, user: user, published: true)
      create(:audit_log, user: admin, slug: "unpublish_all_articles",
                         data: { target_article_ids: [article.id], target_user_id: user.id })
      get "#{admin_user_path(user.id)}?tab=unpublish_logs"
      expect(response.body).to include(CGI.escapeHTML(article.title))
      expect(response.body).to include("(was republished)")
    end

    it "displays nothing on unpublish_alls tab if it the log doesn't exist" do
      get "#{admin_user_path(user.id)}?tab=unpublish_logs"
      expect(response).to be_successful
      expect(response.body).not_to include("Unpublished by")
    end
  end

  describe "POST /admin/member_manager/users/:id/banish" do
    it "bans user for spam" do
      allow(Moderator::BanishUserWorker).to receive(:perform_async)
      post banish_admin_user_path(user.id)
      expect(Moderator::BanishUserWorker).to have_received(:perform_async).with(admin.id, user.id)
      expect(request.flash[:success]).to include("This user is being banished in the background")
    end
  end

  describe "POST /admin/member_manager/users/:id/send_email" do
    let(:params) do
      {
        email_body: "Body",
        email_subject: "subject",
        user_id: user.id.to_s
      }
    end
    let(:mailer) { double }
    let(:message_delivery) { double }

    before do
      allow(ForemInstance).to receive(:smtp_enabled?).and_return(true)
    end

    context "when interacting via a browser" do
      it "returns not found for non existing users" do
        expect { post send_email_admin_user_path(9999), params: params }.to raise_error(ActiveRecord::RecordNotFound)
      end

      it "fails sending the email if an error occurs", :aggregate_failures do
        allow(NotifyMailer).to receive(:with).with(params).and_return(mailer)
        allow(mailer).to receive(:user_contact_email).and_return(message_delivery)
        allow(message_delivery).to receive(:deliver_now).and_return(false)

        assert_no_emails do
          post send_email_admin_user_path(user.id), params: params
        end

        expect(response).to redirect_to(admin_user_path)
        expect(flash[:danger]).to include("failed")
      end

      it "sends an email to the user", :aggregate_failures do
        assert_emails(1) do
          post send_email_admin_user_path(user.id), params: params
        end

        expect(response).to redirect_to(admin_user_path)
        expect(flash[:success]).to include("sent")

        email = ActionMailer::Base.deliveries.last
        expect(email.subject).to eq(params[:email_subject])
        expect(email.text_part.body).to include(params[:email_body])
      end
    end

    context "when interacting via ajax" do
      it "returns not found for non existing users" do
        expect do
          post send_email_admin_user_path(9999), params: params, xhr: true
        end.to raise_error(ActiveRecord::RecordNotFound)
      end

      it "fails sending the email if an error occurs", :aggregate_failures do
        allow(NotifyMailer).to receive(:with).with(params).and_return(mailer)
        allow(mailer).to receive(:user_contact_email).and_return(message_delivery)
        allow(message_delivery).to receive(:deliver_now).and_return(false)

        assert_no_emails do
          post send_email_admin_user_path(user.id), params: params, xhr: true
        end

        expect(response).to have_http_status(:service_unavailable)
        expect(response.parsed_body["error"]).to include("failed")
      end

      it "sends an email to the user", :aggregate_failures do
        assert_emails(1) do
          post send_email_admin_user_path(user.id), params: params, xhr: true
        end

        expect(response).to have_http_status(:ok)
        expect(response.parsed_body["result"]).to include("sent")

        email = ActionMailer::Base.deliveries.last
        expect(email.subject).to eq(params[:email_subject])
        expect(email.text_part.body).to include(params[:email_body])
      end
    end
  end

  describe "POST /admin/member_manager/users/:id/send_email_confirmation" do
    let(:user) { create(:user) }
    let(:message_delivery) { instance_double(ActionMailer::MessageDelivery) }

    before do
      allow(ForemInstance).to receive(:smtp_enabled?).and_return(true)
    end

    context "when interacting via a browser" do
      it "returns not found for non-existing users" do
        expect do
          post send_email_confirmation_admin_user_path(9999)
        end.to raise_error(ActiveRecord::RecordNotFound)
      end

      it "fails sending the confirmation email if an error occurs" do
        allow(User).to receive(:find).with(user.id.to_s).and_return(user)
        allow(user).to receive(:send_confirmation_instructions).and_return(false)

        post send_email_confirmation_admin_user_path(user)

        expect(response).to redirect_to(admin_user_path(user))
        expect(flash[:danger]).to include("failed")
      end

      it "sends the confirmation email successfully" do
        allow(User).to receive(:find).with(user.id.to_s).and_return(user)
        allow(user).to receive(:send_confirmation_instructions).and_return(true)

        post send_email_confirmation_admin_user_path(user)

        expect(response).to redirect_to(admin_user_path(user))
        expect(flash[:success]).to include("sent")
      end
    end

    context "when interacting via AJAX" do
      it "returns not found for non-existing users" do
        expect do
          post send_email_confirmation_admin_user_path(9999), xhr: true
        end.to raise_error(ActiveRecord::RecordNotFound)
      end

      it "fails sending the confirmation email if an error occurs" do
        allow(User).to receive(:find).with(user.id.to_s).and_return(user)
        allow(user).to receive(:send_confirmation_instructions).and_return(false)

        post send_email_confirmation_admin_user_path(user), xhr: true

        expect(response).to have_http_status(:service_unavailable)
        expect(response.parsed_body["error"]).to include("failed")
      end

      it "sends the confirmation email successfully" do
        allow(User).to receive(:find).with(user.id.to_s).and_return(user)
        allow(user).to receive(:send_confirmation_instructions).and_return(true)

        post send_email_confirmation_admin_user_path(user), xhr: true

        expect(response).to have_http_status(:ok)
        expect(response.parsed_body["result"]).to include("sent")
      end
    end
  end

  describe "POST /admin/member_manager/users/:id/verify_email_ownership" do
    let(:mailer) { double }
    let(:message_delivery) { double }

    before do
      allow(ForemInstance).to receive(:smtp_enabled?).and_return(true)
    end

    context "when interacting via a browser" do
      it "returns not found for non existing users" do
        expect do
          post verify_email_ownership_admin_user_path(9999), params: { user_id: user.id }
        end.to raise_error(ActiveRecord::RecordNotFound)
      end

      it "fails sending the email if an error occurs", :aggregate_failures do
        allow(VerificationMailer).to receive(:with).with(user_id: user.id.to_s).and_return(mailer)
        allow(mailer).to receive(:account_ownership_verification_email).and_return(message_delivery)
        allow(message_delivery).to receive(:deliver_now).and_return(false)

        assert_no_emails do
          post verify_email_ownership_admin_user_path(user), params: { user_id: user.id }
        end

        expect(response).to redirect_to(admin_user_path)
        expect(flash[:danger]).to include("failed")
      end

      it "sends an email", :aggregate_failures do
        assert_emails(1) do
          post verify_email_ownership_admin_user_path(user), params: { user_id: user.id }
        end

        expect(response).to redirect_to(admin_user_path)
        expect(flash[:success]).to include("sent")
      end

      it "allows a user to verify email ownership", :aggregate_failures do
        post verify_email_ownership_admin_user_path(user), params: { user_id: user.id }

        path = verify_email_authorizations_path(
          confirmation_token: user.email_authorizations.first.confirmation_token,
          username: user.username,
        )
        verification_link = app_url(path)

        email = ActionMailer::Base.deliveries.last
        expect(email.subject).to eq("Verify Your #{Settings::Community.community_name} Account Ownership")
        expect(email.text_part.body).to include(verification_link)

        sign_in(user)
        get verification_link
        expect(user.email_authorizations.last.verified_at)
          .to be_within(1.minute)
          .of Time.current
      end
    end

    context "when interacting via ajax" do
      it "returns not found for non existing users" do
        expect do
          post verify_email_ownership_admin_user_path(9999), params: { user_id: user.id }, xhr: true
        end.to raise_error(ActiveRecord::RecordNotFound)
      end

      it "fails sending the email if an error occurs", :aggregate_failures do
        allow(VerificationMailer).to receive(:with).with(user_id: user.id.to_s).and_return(mailer)
        allow(mailer).to receive(:account_ownership_verification_email).and_return(message_delivery)
        allow(message_delivery).to receive(:deliver_now).and_return(false)

        assert_no_emails do
          post verify_email_ownership_admin_user_path(user), params: { user_id: user.id }, xhr: true
        end

        expect(response).to have_http_status(:service_unavailable)
        expect(response.parsed_body["error"]).to include("failed")
      end

      it "sends an email", :aggregate_failures do
        assert_emails(1) do
          post verify_email_ownership_admin_user_path(user), params: { user_id: user.id }, xhr: true
        end

        expect(response).to have_http_status(:ok)
        expect(response.parsed_body["result"]).to include("sent")
      end

      it "allows a user to verify email ownership", :aggregate_failures do
        post verify_email_ownership_admin_user_path(user), params: { user_id: user.id }, xhr: true

        path = verify_email_authorizations_path(
          confirmation_token: user.email_authorizations.first.confirmation_token,
          username: user.username,
        )
        verification_link = app_url(path)

        email = ActionMailer::Base.deliveries.last
        expect(email.subject).to eq("Verify Your #{Settings::Community.community_name} Account Ownership")
        expect(email.text_part.body).to include(verification_link)

        sign_in(user)
        get verification_link
        expect(user.email_authorizations.last.verified_at)
          .to be_within(1.minute)
          .of Time.current
      end
    end
  end

  describe "POST /admin/member_manager/users/:id/unpublish_all_articles" do
    let(:target_user) { create(:user) }
    let!(:target_articles) { create_list(:article, 3, user: target_user, published: true) }
    let!(:target_comments) { create_list(:comment, 3, user: target_user) }

    it "creates a corresponding note if note content passed" do
      text = "The articles were not interesting"
      expect do
        post unpublish_all_articles_admin_user_path(target_user.id, note: { content: text })
      end.to change(Note, :count).by(1)
      note = target_user.notes.last
      expect(note.content).to eq(text)
      expect(note.reason).to eq("unpublish_all_articles")
      expect(note.author_id).to eq(admin.id)
    end

    it "unpublishes all articles" do
      allow(Moderator::UnpublishAllArticlesWorker).to receive(:perform_async)
      post unpublish_all_articles_admin_user_path(target_user.id)
      expect(Moderator::UnpublishAllArticlesWorker).to have_received(:perform_async).with(target_user.id, admin.id,
                                                                                          "moderator")
    end

    it "unpublishes users comments and posts" do
      # User's articles are published and comments exist
      expect(target_articles.map(&:published?)).to contain_exactly(true, true, true)
      expect(target_comments.map(&:deleted)).to contain_exactly(false, false, false)

      sidekiq_perform_enqueued_jobs(only: Moderator::UnpublishAllArticlesWorker) do
        post unpublish_all_articles_admin_user_path(target_user.id)
      end

      # Ensure article's aren't published and comments deleted
      # (with boolean attribute so they can be reverted if needed)
      expect(target_articles.map { |a| a.reload.published? }).to contain_exactly(false, false, false)
      expect(target_comments.map { |c| c.reload.deleted? }).to contain_exactly(true, true, true)
    end

    it "creates a log record" do
      Audit::Subscribe.listen :moderator

      create(:article, user: target_user, published: false)
      create(:comment, user: target_user, deleted: true)

      expect do
        sidekiq_perform_enqueued_jobs(only: Moderator::UnpublishAllArticlesWorker) do
          post unpublish_all_articles_admin_user_path(target_user.id)
        end
      end.to change(AuditLog, :count).by(1)

      log = AuditLog.last
      expect(log.category).to eq(AuditLog::MODERATOR_AUDIT_LOG_CATEGORY)
      expect(log.data["action"]).to eq("unpublish_all_articles")
      expect(log.user_id).to eq(admin.id)

      # These ids match the affected articles/comments and not the ones created above
      expect(log.data["target_article_ids"]).to match_array(target_articles.map(&:id))
      expect(log.data["target_comment_ids"]).to match_array(target_comments.map(&:id))

      Audit::Subscribe.forget :moderator
    end
  end

  describe "DELETE /admin/member_manager/users/:id/remove_identity" do
    let(:provider) { Authentication::Providers.available.first }
    let(:user) do
      omniauth_mock_providers_payload
      create(:user, :with_identity)
    end

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

    it "removes the given identity" do
      identity = user.identities.first

      delete remove_identity_admin_user_path(user.id), params: { user: { identity_id: identity.id } }

      expect { identity.reload }.to raise_error(ActiveRecord::RecordNotFound)
    end

    it "updates their social account's username to nil" do
      identity = user.identities.first

      delete remove_identity_admin_user_path(user.id), params: { user: { identity_id: identity.id } }

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

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

      identity = user.identities.twitter.first

      expect do
        delete remove_identity_admin_user_path(user.id), params: { user: { identity_id: identity.id } }
      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)

      identity = user.identities.github.first

      expect do
        delete remove_identity_admin_user_path(user.id), params: { user: { identity_id: identity.id } }
      end.to change(user.github_repos, :count).by(-1)

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

  describe "PATCH admin/users/:id/unlock_access" do
    it "unlocks a locked user account" do
      user.lock_access!
      expect do
        patch unlock_access_admin_user_path(user)
      end.to change { user.reload.access_locked? }.from(true).to(false)
    end
  end

  describe "POST /admin/member_manager/users/:id/export_data" do
    it "redirects properly to the user edit page" do
      sign_in admin
      post export_data_admin_user_path(user), params: { send_to_admin: "true" }
      expect(response).to redirect_to admin_user_path(user)
    end
  end
end
