require "spec_helper"

describe InboxController do
  include LoginMacros
  include RedirectExpectationHelper
  let(:user) { create(:user) }

  describe "GET #show" do
    it "redirects to user page when not logged in and displays an error" do
      get :show, params: { user_id: user.login }
      it_redirects_to_with_error(user_path(user),
                                 "Sorry, you don't have permission to access the page you were trying to reach. Please log in.")
    end

    it "redirects to user page when logged in as another user and displays an error" do
      fake_login_known_user(create(:user))
      get :show, params: { user_id: user.login }
      it_redirects_to_with_error(user_path(user),
                                 "Sorry, you don't have permission to access the page you were trying to reach.")
    end

    context "when logged in as an admin" do
      context "when the admin does not have the correct authorization" do
        context "when the admin has no role" do
          let(:admin) { create(:admin, roles: []) }

          before { fake_login_admin(admin) }

          it "redirects with error" do
            get :show, params: { user_id: user.login }
  
            it_redirects_to_with_error(root_path, "Sorry, only an authorized admin can access the page you were trying to reach.")
          end
        end
  
        (Admin::VALID_ROLES - %w[superadmin policy_and_abuse]).each do |role|
          context "when the admin has the #{role} role" do
            let(:admin) { create(:admin, roles: [role]) }
            
            before { fake_login_admin(admin) }
            
            it "redirects with error" do
              get :show, params: { user_id: user.login }
  
              it_redirects_to_with_error(root_path, "Sorry, only an authorized admin can access the page you were trying to reach.")
            end
          end
        end
      end

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

          it "renders the user inbox" do
            get :show, params: { user_id: user.login }
            expect(response).to render_template("show")
            expect(assigns(:inbox_total)).to eq(0)
            expect(assigns(:unread)).to eq(0)
          end

          context "with unread comments" do
            let!(:inbox_comments) do
              Array.new(3) do |i|
                create(:inbox_comment, user: user, created_at: Time.now.utc + i.days)
              end
            end

            it "renders non-zero unread count" do
              get :show, params: { user_id: user.login }
              expect(assigns(:inbox_comments)).to eq(inbox_comments.reverse)
              expect(assigns(:inbox_total)).to eq(3)
              expect(assigns(:unread)).to eq(3)
            end

            it "renders oldest first" do
              get :show, params: { user_id: user.login, filters: { date: "asc" } }
              expect(assigns(:filters)[:date]).to eq("asc")
              expect(assigns(:inbox_comments)).to eq(inbox_comments)
              expect(assigns(:inbox_total)).to eq(3)
              expect(assigns(:unread)).to eq(3)
            end
          end

          context "with 1 read and 1 unread" do
            let!(:read_comment) { create(:inbox_comment, user: user, read: true) }
            let!(:unread_comment) { create(:inbox_comment, user: user) }

            it "renders only unread" do
              get :show, params: { user_id: user.login, filters: { read: "false" } }
              expect(assigns(:filters)[:read]).to eq("false")
              expect(assigns(:inbox_comments)).to eq([unread_comment])
              expect(assigns(:inbox_total)).to eq(2)
              expect(assigns(:unread)).to eq(1)
            end
          end

          context "with 1 replied and 1 unreplied" do
            let!(:replied_comment) { create(:inbox_comment, user: user, replied_to: true) }
            let!(:unreplied_comment) { create(:inbox_comment, user: user) }

            it "renders only unreplied" do
              get :show, params: { user_id: user.login, filters: { replied_to: "false" } }
              expect(assigns(:filters)[:replied_to]).to eq("false")
              expect(assigns(:inbox_comments)).to eq([unreplied_comment])
              expect(assigns(:inbox_total)).to eq(2)
              expect(assigns(:unread)).to eq(2)
            end
          end

          context "with a deleted comment" do
            let(:inbox_comment) { create(:inbox_comment, user: user) }

            it "excludes deleted comments" do
              inbox_comment.feedback_comment.destroy!
              get :show, params: { user_id: user.login }
              expect(assigns(:inbox_total)).to eq(0)
              expect(assigns(:unread)).to eq(0)
            end
          end
        end
      end
    end

    context "when logged in as the same user" do
      before { fake_login_known_user(user) }

      it "renders the user inbox" do
        get :show, params: { user_id: user.login }
        expect(response).to render_template("show")
        expect(assigns(:inbox_total)).to eq(0)
        expect(assigns(:unread)).to eq(0)
      end

      context "with unread comments" do
        let!(:inbox_comments) do
          Array.new(3) do |i|
            create(:inbox_comment, user: user, created_at: Time.now + i.days)
          end
        end

        it "renders non-zero unread count" do
          get :show, params: { user_id: user.login }
          expect(assigns(:inbox_comments)).to eq(inbox_comments.reverse)
          expect(assigns(:inbox_total)).to eq(3)
          expect(assigns(:unread)).to eq(3)
        end

        it "renders oldest first" do
          get :show, params: { user_id: user.login, filters: { date: "asc" } }
          expect(assigns(:filters)[:date]).to eq("asc")
          expect(assigns(:inbox_comments)).to eq(inbox_comments)
          expect(assigns(:inbox_total)).to eq(3)
          expect(assigns(:unread)).to eq(3)
        end
      end

      context "with 1 read and 1 unread" do
        let!(:read_comment) { create(:inbox_comment, user: user, read: true) }
        let!(:unread_comment) { create(:inbox_comment, user: user) }

        it "renders only unread" do
          get :show, params: { user_id: user.login, filters: { read: "false" } }
          expect(assigns(:filters)[:read]).to eq("false")
          expect(assigns(:inbox_comments)).to eq([unread_comment])
          expect(assigns(:inbox_total)).to eq(2)
          expect(assigns(:unread)).to eq(1)
        end
      end

      context "with 1 replied and 1 unreplied" do
        let!(:replied_comment) { create(:inbox_comment, user: user, replied_to: true) }
        let!(:unreplied_comment) { create(:inbox_comment, user: user) }

        it "renders only unreplied" do
          get :show, params: { user_id: user.login, filters: { replied_to: "false" } }
          expect(assigns(:filters)[:replied_to]).to eq("false")
          expect(assigns(:inbox_comments)).to eq([unreplied_comment])
          expect(assigns(:inbox_total)).to eq(2)
          expect(assigns(:unread)).to eq(2)
        end
      end

      context "with a deleted comment" do
        let(:inbox_comment) { create(:inbox_comment, user: user) }

        it "excludes deleted comments" do
          inbox_comment.feedback_comment.destroy!
          get :show, params: { user_id: user.login }
          expect(assigns(:inbox_total)).to eq(0)
          expect(assigns(:unread)).to eq(0)
        end
      end
    end
  end

  describe "GET #reply" do
    let(:inbox_comment) { create(:inbox_comment) }
    let(:feedback_comment) { inbox_comment.feedback_comment }
    let(:user) { inbox_comment.user }
    let(:params) { { user_id: user, comment_id: feedback_comment.id } }

    before { fake_login_known_user(inbox_comment.user) }

    it "renders the HTML form" do
      get :reply, params: params
      path_params = {
        add_comment_reply_id: feedback_comment.id,
        anchor: "comment_" + feedback_comment.id.to_s
      }
      it_redirects_to(comment_path(feedback_comment, path_params))
      expect(assigns(:commentable)).to eq(feedback_comment)
      expect(assigns(:comment)).to be_a_new(Comment)
    end

    it "renders the JS form" do
      get :reply, params: params.merge(format: :js), xhr: true
      expect(response).to render_template("reply")
      expect(assigns(:commentable)).to eq(feedback_comment)
      expect(assigns(:comment)).to be_a_new(Comment)
    end

    context "when the user is blocked" do
      before { Block.create!(blocker: blocker, blocked: user) }

      context "by the person they're replying to" do
        let(:blocker) { feedback_comment.user }

        it "redirects to the user's inbox" do
          get :reply, params: params

          it_redirects_to_with_error(
            user_inbox_path(user),
            "Sorry, you have been blocked by that user."
          )
        end
      end

      context "by the work creator" do
        let(:blocker) { feedback_comment.ultimate_parent.users.first }

        it "redirects to the user's inbox" do
          get :reply, params: params

          it_redirects_to_with_error(
            user_inbox_path(user),
            "Sorry, you have been blocked by one or more of this work's creators."
          )
        end
      end
    end
  end

  describe "PUT #update" do
    %w[superadmin policy_and_abuse].each do |role|
      context "when logged in as an admin with the role #{role}" do
        let(:admin) { create(:admin, roles: [role]) }
            
        before { fake_login_admin(admin) }
 
        it "redirects to root with error" do
          put :update, params: { user_id: user.login }
          it_redirects_to_with_error(root_path, "Sorry, only an authorized admin can access the page you were trying to reach.")
        end
      end
    end

    context "when logged in as the comment receiver" do
      before { fake_login_known_user(user) }

      context "with no comments selected" do
        it "redirects to the user's inbox with a caution" do
          put :update, params: { user_id: user.login, read: "yeah" }
          it_redirects_to_with_caution(user_inbox_path(user), "Please select at least one comment first.")
        end

        it "redirects to the previously viewed page if HTTP_REFERER is set, with a caution" do
          @request.env["HTTP_REFERER"] = root_path
          put :update, params: { user_id: user.login, read: "yeah" }
          it_redirects_to_with_caution(root_path, "Please select at least one comment first.")
        end
      end

      context "with unread comments" do
        let!(:inbox_comment1) { create(:inbox_comment, user: user) }
        let!(:inbox_comment2) { create(:inbox_comment, user: user) }

        it "marks all as read and redirects to inbox with a notice" do
          parameters = {
            user_id: user.login,
            inbox_comments: [inbox_comment1.id, inbox_comment2.id],
            read: "yeah"
          }

          put :update, params: parameters
          it_redirects_to_with_notice(user_inbox_path(user), "Inbox successfully updated.")

          inbox_comment1.reload
          expect(inbox_comment1.read).to be_truthy
          inbox_comment2.reload
          expect(inbox_comment2.read).to be_truthy
        end

        it "marks one as read and redirects to inbox with a notice" do
          put :update, params: { user_id: user.login, inbox_comments: [inbox_comment1.id], read: "yeah" }
          it_redirects_to_with_notice(user_inbox_path(user), "Inbox successfully updated.")

          inbox_comment1.reload
          expect(inbox_comment1.read).to be_truthy
          inbox_comment2.reload
          expect(inbox_comment2.read).to be_falsy
        end

        it "deletes one and redirects to inbox with a notice" do
          put :update, params: { user_id: user.login, inbox_comments: [inbox_comment1.id], delete: "yeah" }
          it_redirects_to_with_notice(user_inbox_path(user), "Inbox successfully updated.")

          expect(InboxComment.find_by(id: inbox_comment1.id)).to be_nil
          inbox_comment2.reload
          expect(inbox_comment2.read).to be_falsy
        end
      end

      context "with a read comment and redirects to inbox with a notice" do
        let!(:inbox_comment) { create(:inbox_comment, user: user, read: true) }

        it "marks as unread and redirects to inbox with a notice" do
          put :update, params: { user_id: user.login, inbox_comments: [inbox_comment.id], unread: "yeah" }
          it_redirects_to_with_notice(user_inbox_path(user), "Inbox successfully updated.")

          inbox_comment.reload
          expect(inbox_comment.read).to be_falsy
        end

        it "marks as unread and returns a JSON response" do
          parameters = {
            user_id: user.login,
            inbox_comments: [inbox_comment.id],
            unread: "yeah",
            format: "json"
          }

          put :update, params: parameters

          inbox_comment.reload
          expect(inbox_comment.read).to be_falsy

          parsed_body = JSON.parse(response.body, symbolize_names: true)
          expect(parsed_body[:item_success_message]).to eq("Inbox successfully updated.")
          expect(response).to have_http_status(:success)
        end
      end
    end
  end
end
