# frozen_string_literal: true

require "spec_helper"

describe Admin::AdminUsersController do
  include LoginMacros
  include RedirectExpectationHelper

  let(:admin) { create(:admin) }
  let(:user) { create(:user) }

  manage_roles = %w[superadmin legal open_doors policy_and_abuse support tag_wrangling].freeze
  search_roles = %w[superadmin legal open_doors policy_and_abuse support tag_wrangling].freeze
  search_past_roles = %w[superadmin open_doors policy_and_abuse support tag_wrangling].freeze

  shared_examples "an action unauthorized admins can't access" do |authorized_roles:|
    before { fake_login_admin(admin) }

    context "with no role" do
      let(:admin) { create(:admin, roles: []) }

      it "redirects with an error" do
        subject.call
        it_redirects_to_with_error(root_url, "Sorry, only an authorized admin can access the page you were trying to reach.")
      end
    end

    (Admin::VALID_ROLES - authorized_roles).each do |role|
      context "with role #{role}" do
        let(:admin) { create(:admin, roles: [role]) }

        it "redirects with an error" do
          subject.call
          it_redirects_to_with_error(root_url, "Sorry, only an authorized admin can access the page you were trying to reach.")
        end
      end
    end
  end

  shared_examples "an action guests and logged in users can't access" do
    it "redirects with notice when logged out" do
      subject.call
      it_redirects_to_with_notice(root_url, "I'm sorry, only an admin can look at that area")
    end

    it "redirects with notice when logged in" do
      fake_login
      subject.call
      it_redirects_to_with_notice(root_url, "I'm sorry, only an admin can look at that area")
    end
  end

  shared_examples "an action authorized admins can access" do |authorized_roles:|
    before { fake_login_admin(admin) }

    authorized_roles.each do |role|
      context "with role #{role}" do
        let(:admin) { create(:admin, roles: [role]) }

        it "responds with HTTP success status" do
          subject.call
          expect(response).to have_http_status(:success)
        end
      end
    end
  end

  describe "GET #index" do
    subject { -> { get :index } }

    it_behaves_like "an action guests and logged in users can't access"

    context "when logged in as admin" do
      it_behaves_like "an action unauthorized admins can't access",
                      authorized_roles: search_roles

      it_behaves_like "an action authorized admins can access",
                      authorized_roles: search_roles
    end

    context "when passing search_past", user_search: true do
      subject { -> { get :index, params: { name: "old", search_past: "1" } } }
      let(:user) { create(:user, login: "old") }

      before do
        user.update!(login: "new")
        run_all_indexing_jobs
      end

      shared_examples "a search unauthorized admins can't perform" do |authorized_roles:|
        (Admin::VALID_ROLES - authorized_roles).each do |role|
          context "with role #{role}" do
            let(:admin) { create(:admin, roles: [role]) }

            it "does not find the user" do
              subject.call
              expect(assigns(:users)).to be_nil
            end
          end
        end
      end

      shared_examples "a search authorized admins can perform" do |authorized_roles:|
        before { fake_login_admin(admin) }

        authorized_roles.each do |role|
          context "with role #{role}" do
            let(:admin) { create(:admin, roles: [role]) }

            it "finds the user" do
              subject.call
              expect(assigns(:users)).to include(user)
            end
          end
        end
      end

      it_behaves_like "a search unauthorized admins can't perform",
                      authorized_roles: search_past_roles

      it_behaves_like "a search authorized admins can perform",
                      authorized_roles: search_past_roles
    end
  end

  describe "GET #bulk_search" do
    subject { -> { get :bulk_search } }

    it_behaves_like "an action guests and logged in users can't access"

    context "when logged in as admin" do
      it_behaves_like "an action unauthorized admins can't access",
                      authorized_roles: search_roles

      it_behaves_like "an action authorized admins can access",
                      authorized_roles: search_roles
    end
  end

  describe "GET #show" do
    subject { -> { get :show, params: { id: user.login } } }

    it_behaves_like "an action guests and logged in users can't access"

    context "when logged in as admin" do
      it_behaves_like "an action unauthorized admins can't access",
                      authorized_roles: search_roles

      it_behaves_like "an action authorized admins can access",
                      authorized_roles: search_roles
    end

    context "when admin has correct authorization" do
      let(:admin) { create(:policy_and_abuse_admin) }

      it "if user exists, allows access to show page" do
        fake_login_admin(admin)
        get :show, params: { id: user.login }

        expect(response).to have_http_status(:success)
      end

      it "if user does not exist, raises a 404" do
        fake_login_admin(admin)
        params = { id: "not_existing_id" }

        expect { get :show, params: params }
          .to raise_error ActiveRecord::RecordNotFound
      end
    end
  end

  describe "PUT #update" do
    subject { -> { put :update, params: { id: user.login, user: { roles: [] } } } }
    let(:old_role) { create(:role) }
    let(:role) { create(:role) }
    let(:user) { create(:user, email: "user@example.com", roles: [old_role]) }

    it_behaves_like "an action guests and logged in users can't access"

    context "when logged in as an unauthorized admin" do
      it_behaves_like "an action unauthorized admins can't access",
                      authorized_roles: manage_roles
    end

    context "when logged in as an authorized admin" do
      before do
        fake_login_admin(admin)
      end

      %w[policy_and_abuse superadmin].each do |admin_role|
        context "with role #{admin_role}" do
          before do
            admin.update!(roles: [admin_role])
            old_role.update!(name: "no_resets")
            role.update!(name: "protected_user")
          end

          it "allows admins to update all attributes" do
            expect do
              put :update, params: {
                id: user.login,
                user: {
                  email: "updated@example.com",
                  roles: [role.id.to_s]
                }
              }
            end.to change { user.reload.roles.pluck(:name) }
              .from([old_role.name])
              .to([role.name])
              .and change { user.reload.email }
              .from("user@example.com")
              .to("updated@example.com")

            it_redirects_to_with_notice(root_path, "User was successfully updated.")
          end
        end
      end

      context "with role open_doors" do
        before do
          admin.update!(roles: ["open_doors"])
          old_role.update!(name: "archivist")
          role.update!(name: "opendoors")
        end

        it "does not allow updating email" do
          expect do
            put :update, params: { id: user.login, user: { email: "updated@example.com" } }
          end.to raise_exception(ActionController::UnpermittedParameters)
          expect(user.reload.email).not_to eq("updated@example.com")
        end

        it "allows updating roles" do
          role_no_resets = create(:role, name: "no_resets")
          expect do
            put :update, params: { id: user.login, user: { roles: [role.id.to_s, role_no_resets.id.to_s] } }
          end.to change { user.reload.roles.pluck(:name) }
            .from([old_role.name])
            .to([role.name, role_no_resets.name])
            .and avoid_changing { user.reload.email }

          it_redirects_to_with_notice(root_path, "User was successfully updated.")
        end
      end

      context "with role support" do
        before do
          admin.update!(roles: ["support"])
          user.update!(roles: [])
          role.update!(name: "no_resets")
        end

        it "allows updating email" do
          put :update, params: { id: user.login, user: { email: "updated@example.com" } }
          expect(user.reload.email).to eq("updated@example.com")
        end

        it "allows updating roles" do
          expect do
            put :update, params: { id: user.login, user: { roles: [role.id.to_s] } }
          end.to change { user.reload.roles.pluck(:name) }
            .from([])
            .to([role.name])
            .and avoid_changing { user.reload.email }

          it_redirects_to_with_notice(root_path, "User was successfully updated.")
        end
      end

      context "with role tag_wrangling" do
        before do
          admin.update!(roles: ["tag_wrangling"])
          user.update!(roles: [])
          role.update!(name: "tag_wrangler")
        end

        it "does not allow updating email" do
          expect do
            put :update, params: { id: user.login, user: { email: "updated@example.com" } }
          end.to raise_exception(ActionController::UnpermittedParameters)
          expect(user.reload.email).not_to eq("updated@example.com")
        end

        it "allows updating roles" do
          expect do
            put :update, params: { id: user.login, user: { roles: [role.id.to_s] } }
          end.to change { user.reload.roles.pluck(:name) }
            .from([])
            .to([role.name])
            .and avoid_changing { user.reload.email }

          it_redirects_to_with_notice(root_path, "User was successfully updated.")
        end
      end

      %w[legal].each do |admin_role|
        context "with role #{admin_role}" do
          let(:admin) { create(:admin, roles: [admin_role]) }

          it "does not allow admin updating roles" do
            expect do
              put :update, params: { id: user.login, user: { roles: [role.id.to_s] } }
            end.to raise_exception(ActionController::UnpermittedParameters)
            expect(user.reload.roles).not_to include(role)
          end

          it "does not allow updating email" do
            expect do
              put :update, params: { id: user.login, user: { email: "updated@example.com" } }
            end.to raise_exception(ActionController::UnpermittedParameters)
            expect(user.reload.email).not_to eq("updated@example.com")
          end
        end
      end

      context "with available user roles restricted by type of admin" do
        let!(:opendoors_role) { create(:role, name: "opendoors") }
        let!(:archivist_role) { create(:role, name: "archivist") }
        let!(:tag_wrangler_role) { create(:role, name: "tag_wrangler") }

        before { admin.update!(roles: ["open_doors"]) }

        it "does not add unpermitted roles" do
          user.update!(roles: [opendoors_role])

          put :update, params: {
            id: user.login,
            user: {
              roles: [opendoors_role.id.to_s, tag_wrangler_role.id.to_s]
            }
          }
          it_redirects_to_with_notice(root_path, "User was successfully updated.")
          expect(user.reload.roles).to eq([opendoors_role])
        end

        it "does not change unpermitted roles when adding a role" do
          user.update!(roles: [tag_wrangler_role])

          put :update, params: {
            id: user.login,
            user: {
              roles: [opendoors_role.id.to_s]
            }
          }
          it_redirects_to_with_notice(root_path, "User was successfully updated.")
          expect(user.reload.roles).to eq([opendoors_role, tag_wrangler_role])
        end

        it "does not change unpermitted roles when removing a role" do
          user.update!(roles: [tag_wrangler_role, opendoors_role])

          put :update, params: {
            id: user.login,
            user: {
              roles: [""]
            }
          }
          it_redirects_to_with_notice(root_path, "User was successfully updated.")
          expect(user.reload.roles).to eq([tag_wrangler_role])
        end
      end
    end
  end

  describe "POST #update_next_of_kin" do
    subject { -> { post :update_next_of_kin, params: { user_login: user.login, next_of_kin_name: kin.login, next_of_kin_email: kin.email } } }
    let(:kin) { create(:user) }
    authorized_roles = %w[superadmin policy_and_abuse support].freeze

    it_behaves_like "an action guests and logged in users can't access"

    context "when logged in as an unauthorized admin" do
      it_behaves_like "an action unauthorized admins can't access",
                      authorized_roles: authorized_roles
    end

    context "when logged in as an authorized admin" do
      before do
        fake_login_admin(admin)
      end

      authorized_roles.each do |role|
        context "with role #{role}" do
          let(:admin) { create(:admin, roles: [role]) }

          it "adds next of kin and redirects with notice" do
            subject.call
            it_redirects_to_with_notice(admin_user_path(user), "Fannish next of kin was updated.")
            expect(user.reload.fannish_next_of_kin.kin).to eq(kin)
            expect(user.reload.fannish_next_of_kin.kin_email).to eq(kin.email)
          end

          it "logs adding a fannish next of kin" do
            subject.call
            user.reload
            expect(user.fannish_next_of_kin.kin).to eq(kin)
            log_item = user.log_items.last
            expect(log_item.action).to eq(ArchiveConfig.ACTION_ADD_FNOK)
            expect(log_item.fnok_user.id).to eq(kin.id)

            added_log_item = kin.reload.log_items.last
            expect(added_log_item.action).to eq(ArchiveConfig.ACTION_ADDED_AS_FNOK)
            expect(added_log_item.fnok_user.id).to eq(user.id)

            expect_changes_made_by(admin, [log_item, added_log_item])
          end

          it "logs removing a fannish next of kin" do
            kin = create(:fannish_next_of_kin, user: user).kin

            post :update_next_of_kin, params: {
              user_login: user.login
            }
            user.reload
            expect(user.fannish_next_of_kin).to be_nil
            log_item = user.log_items.last
            expect(log_item.action).to eq(ArchiveConfig.ACTION_REMOVE_FNOK)
            expect(log_item.fnok_user.id).to eq(kin.id)

            removed_log_item = kin.reload.log_items.last
            expect(removed_log_item.action).to eq(ArchiveConfig.ACTION_REMOVED_AS_FNOK)
            expect(removed_log_item.fnok_user.id).to eq(user.id)

            expect_changes_made_by(admin, [log_item, removed_log_item])
          end

          it "logs updating a fannish next of kin" do
            previous_kin = create(:fannish_next_of_kin, user: user).kin

            subject.call
            user.reload
            expect(user.fannish_next_of_kin.kin).to eq(kin)

            remove_log_item = user.log_items[-2]
            expect(remove_log_item.action).to eq(ArchiveConfig.ACTION_REMOVE_FNOK)
            expect(remove_log_item.fnok_user.id).to eq(previous_kin.id)

            add_log_item = user.log_items.last
            expect(add_log_item.action).to eq(ArchiveConfig.ACTION_ADD_FNOK)
            expect(add_log_item.fnok_user.id).to eq(kin.id)

            removed_log_item = previous_kin.reload.log_items.last
            expect(removed_log_item.action).to eq(ArchiveConfig.ACTION_REMOVED_AS_FNOK)
            expect(removed_log_item.fnok_user.id).to eq(user.id)

            added_log_item = kin.reload.log_items.last
            expect(added_log_item.action).to eq(ArchiveConfig.ACTION_ADDED_AS_FNOK)
            expect(added_log_item.fnok_user.id).to eq(user.id)

            expect_changes_made_by(admin, [remove_log_item, add_log_item, removed_log_item, added_log_item])
          end

          def expect_changes_made_by(admin, log_items)
            log_items.each do |log_item|
              expect(log_item.admin_id).to eq(admin.id)
              expect(log_item.note).to eq("Change made by #{admin.login}")
            end
          end

          it "does nothing if changing the fnok to themselves" do
            previous_kin = create(:fannish_next_of_kin, user: user)

            post :update_next_of_kin, params: {
              user_login: user.login, next_of_kin_name: previous_kin.kin.login, next_of_kin_email: previous_kin.kin_email
            }
            it_redirects_to_with_notice(admin_user_path(user), "No change to fannish next of kin.")
            expect(user.reload.log_items).to be_empty
          end

          it "errors if trying to add an incomplete fnok" do
            post :update_next_of_kin, params: {
              user_login: user.login, next_of_kin_email: ""
            }

            kin = assigns(:user).fannish_next_of_kin
            expect(kin).not_to be_valid
            expect(kin.errors[:kin_email]).to include("can't be blank")

            expect(user.reload.log_items).to be_empty
          end
        end
      end
    end
  end

  describe "POST #update_status" do
    before { fake_login_admin(admin) }

    shared_examples "an unauthorized admin adding a note to a user" do
      it "redirects with error" do
        post :update_status, params: {
          user_login: user.login, admin_action: "note", admin_note: "User likes me, user likes me not."
        }
        it_redirects_to_with_error(root_path, "Sorry, only an authorized admin can access the page you were trying to reach.")
      end
    end

    shared_examples "an authorized admin adding a note to a user" do
      it "saves note and redirects with notice" do
        admin_note = "User likes me, user likes me not."
        post :update_status, params: {
          user_login: user.login, admin_action: "note", admin_note: admin_note
        }
        it_redirects_to_with_notice(admin_user_path(user), "Note was recorded.")
        expect(user.reload.log_items.last.action).to eq(ArchiveConfig.ACTION_NOTE)
        expect(user.log_items.last.note).to eq(admin_note)
      end
    end

    shared_examples "an unauthorized admin suspending a user" do
      it "redirects with error" do
        post :update_status, params: {
          user_login: user.login, admin_action: "suspend", suspend_days: "3", admin_note: "User violated community guidelines"
        }
        it_redirects_to_with_error(root_path, "Sorry, only an authorized admin can access the page you were trying to reach.")
        expect(user.reload.suspended).to be_falsey
      end
    end

    shared_examples "an authorized admin suspending a user" do
      it "suspends user and redirects with notice" do
        post :update_status, params: {
          user_login: user.login, admin_action: "suspend", suspend_days: "3", admin_note: "User violated community guidelines"
        }
        it_redirects_to_with_notice(admin_user_path(user), "User has been temporarily suspended.")
        expect(user.reload.suspended).to be_truthy
      end
    end

    context "when the admin has no roles" do
      before { admin.update!(roles: []) }

      it_behaves_like "an unauthorized admin adding a note to a user"
      it_behaves_like "an unauthorized admin suspending a user"
    end

    (Admin::VALID_ROLES - %w[policy_and_abuse support superadmin]).each do |role|
      context "when the admin has #{role} role" do
        it_behaves_like "an unauthorized admin adding a note to a user"
        it_behaves_like "an unauthorized admin suspending a user"
      end
    end

    %w[superadmin policy_and_abuse].each do |role|
      context "when the admin has #{role} role" do
        let(:admin) { create(:admin, roles: [role]) }

        it_behaves_like "an authorized admin adding a note to a user"
        it_behaves_like "an authorized admin suspending a user"
      end
    end

    context "when the admin has support role" do
      let(:admin) { create(:support_admin) }

      it_behaves_like "an authorized admin adding a note to a user"
      it_behaves_like "an unauthorized admin suspending a user"
    end
  end

  describe "GET #confirm_delete_user_creations" do
    subject { -> { get :confirm_delete_user_creations, params: { id: user.login } } }
    let(:user) { create(:user, banned: true) }
    authorized_roles = %w[superadmin policy_and_abuse].freeze

    it_behaves_like "an action guests and logged in users can't access"

    context "when logged in as an unauthorized admin" do
      it_behaves_like "an action unauthorized admins can't access",
                      authorized_roles: authorized_roles
    end

    context "when logged in as an authorized admin" do
      before { fake_login_admin(admin) }

      authorized_roles.each do |role|
        context "with role #{role}" do
          let(:admin) { create(:admin, roles: [role]) }

          context "when the user is not banned" do
            it "redirects with error" do
              user.update!(banned: false)
              subject.call

              it_redirects_to_with_error(admin_users_path, "That user is not banned!")
            end
          end

          context "when the user is banned" do
            it "allows admins to access delete user creations page" do
              user.update!(banned: true)
              subject.call

              expect(response).to have_http_status(:success)
            end
          end
        end
      end
    end
  end

  describe "POST #destroy_user_creations" do
    subject { -> { post :destroy_user_creations, params: { id: user.login } } }
    let!(:work) { create(:work, authors: [user.default_pseud]) }
    let(:other_owner) { create(:user, banned: false) }
    let!(:collection1) { create(:collection) }
    let!(:collection2) { create(:collection) }
    let!(:comment) { create(:comment, pseud: user.default_pseud) }

    authorized_roles = %w[superadmin policy_and_abuse].freeze

    before do
      # Banning user only after creating works for them
      user.update!(banned: true)

      collection1.collection_participants = [create(:collection_participant, user: user, collection: collection1, participant_role: CollectionParticipant::OWNER)]
      collection1.save
      create(:collection_participant, user: other_owner, collection: collection2, participant_role: CollectionParticipant::OWNER)
      create(:collection_participant, user: user, collection: collection2, participant_role: CollectionParticipant::MEMBER)
    end

    it_behaves_like "an action guests and logged in users can't access"

    context "when logged in as an unauthorized admin" do
      it_behaves_like "an action unauthorized admins can't access",
                      authorized_roles: authorized_roles
    end

    context "when logged in as an authorized admin" do
      before do
        fake_login_admin(admin)
      end

      authorized_roles.each do |role|
        context "with role #{role}" do
          let(:admin) { create(:admin, roles: [role]) }

          context "when the user is not banned" do
            it "redirects with error" do
              user.update!(banned: false)
              subject.call

              it_redirects_to_with_error(admin_users_path, "That user is not banned!")
            end
          end

          context "when the user is banned" do
            it "destroys the user's creations and redirects with success notice" do
              subject.call
              # Check that the first user's collection is deleted
              expect(Collection.exists?(collection1.id)).to be_falsey
              # Check that the second user's collection still exists
              expect(Collection.exists?(other_owner.collections.last.id)).to be_truthy

              it_redirects_to_with_notice(admin_users_path, "All creations by user #{user.login} have been deleted.")
              expect(Work.exists?(work.id)).to be false
              expect(Comment.exists?(comment.id)).to be_falsey
            end

            it "sends all of the user's comments as spam reports to Akismet" do
              expect_any_instance_of(Comment).to receive(:submit_spam)

              subject.call
            end

            it "does not send deleted comments to Akismet" do
              comment.is_deleted = true
              comment.save!(validate: false)
              expect_any_instance_of(Comment).to_not receive(:submit_spam)

              subject.call
            end

            it "deletes marked-as-deleted comments when all its replies are deleted" do
              reply = create(:comment, commentable: comment, pseud: user.default_pseud)
              comment.destroy_or_mark_deleted

              expect(comment.is_deleted).to be_truthy

              subject.call

              expect(Comment.exists?(comment.id)).to be_falsey
              expect(Comment.exists?(reply.id)).to be_falsey
            end
          end
        end
      end
    end
  end

  describe "GET #troubleshoot" do
    subject { -> { get :troubleshoot, params: { id: user.login } } }

    it_behaves_like "an action guests and logged in users can't access"

    context "when logged in as an unauthorized admin" do
      it_behaves_like "an action unauthorized admins can't access",
                      authorized_roles: manage_roles
    end

    context "when logged in as an authorized admin" do
      manage_roles.each do |role|
        context "with role #{role}" do
          let(:admin) { create(:admin, roles: [role]) }

          it "troubleshoots account and redirects with notice" do
            fake_login_admin(admin)
            subject.call
            it_redirects_to_with_notice(root_path, "User account troubleshooting complete.")
          end
        end
      end
    end
  end

  describe "POST #activate" do
    subject { -> { post :activate, params: { id: user.login } } }

    it_behaves_like "an action guests and logged in users can't access"

    context "when logged in as an unauthorized admin" do
      it_behaves_like "an action unauthorized admins can't access",
                      authorized_roles: manage_roles
    end

    context "when logged in as an authorized admin" do
      manage_roles.each do |role|
        context "with role #{role}" do
          let(:admin) { create(:admin, roles: [role]) }

          it "activates the user account, creates a log item, and redirects with notice" do
            fake_login_admin(admin)
            subject.call
            log_item = user.reload.log_items.last
            expect(log_item.action).to eq(ArchiveConfig.ACTION_ACTIVATE)
            expect(log_item.note).to eq("Manually Activated")
            expect(log_item.admin).to eq(admin)
            it_redirects_to_with_notice(admin_user_path(id: user.login), "User Account Activated")
          end
        end
      end
    end
  end

  describe "GET #creations" do
    subject { -> { get :creations, params: { id: user.login } } }
    authorized_roles = %w[policy_and_abuse superadmin]

    it_behaves_like "an action guests and logged in users can't access"

    context "when logged in as an unauthorized admin" do
      it_behaves_like "an action unauthorized admins can't access",
                      authorized_roles: authorized_roles
    end

    context "when logged in as an authorized admin" do
      authorized_roles.each do |role|
        context "with #{role} role" do
          let(:admin) { create(:admin, roles: [role]) }
          let!(:user_comment) { create(:comment, pseud: user.default_pseud) }
          let!(:other_comment) { create(:comment) }
          let!(:user_work) { create(:work, authors: [user.default_pseud]) }
          let!(:other_work) { create(:work) }

          before { fake_login_admin(admin) }

          it "renders creations template and assigns comments and works" do
            subject.call
            expect(response).to render_template(:creations)
            expect(assigns[:comments]).to contain_exactly(user_comment)
            expect(assigns[:works]).to contain_exactly(user_work)
          end

          it "raises ActiveRecord::RecordNotFound when user does not exist" do
            expect do
              get :creations, params: { id: "nonexistent_user" }
            end.to raise_exception(ActiveRecord::RecordNotFound)
          end
        end
      end
    end
  end
end
