# coding: utf-8
# frozen_string_literal: true

require "rails_helper"

describe Ajax::CommentController, :ajax_controller, type: :controller do
  include ActiveSupport::Testing::TimeHelpers

  let(:answer) { FactoryBot.create(:answer, user: FactoryBot.create(:user)) }

  describe "#create" do
    let(:params) do
      {
        answer:  answer_id,
        comment:
      }.compact
    end

    subject { post(:create, params:) }

    context "when user is signed in" do
      shared_examples "creates the comment" do
        it "creates a comment to the answer" do
          expect { subject }.to(change { Comment.count }.by(1))
          expect(answer.reload.comments.ids).to include(Comment.last.id)
        end

        context "a user is subscribed to the answer" do
          let(:subscribed_user) { FactoryBot.create(:user) }

          it "updates the notification caching timestamp for a subscribed user" do
            Subscription.subscribe(subscribed_user, answer)

            travel_to(1.day.from_now) do
              expect { subject }.to change { subscribed_user.reload.notifications_updated_at }.to(DateTime.now)
            end
          end
        end

        include_examples "returns the expected response"
      end

      shared_examples "does not create the comment" do
        it "does not create a comment" do
          expect { subject }.not_to(change { Comment.count })
        end

        include_examples "returns the expected response"
      end

      before(:each) { sign_in(user) }

      context "when all parameters are given" do
        let(:comment) { "// Here be dragons." }

        context "when answer exists" do
          let(:answer_id) { answer.id }

          let(:expected_response) do
            {
              "success" => true,
              "status"  => "okay",
              "message" => anything,
              "render"  => anything,
              "count"   => 1
            }
          end

          include_examples "creates the comment"

          context "when comment is too long" do
            let(:comment) { "E" * 621 }
            let(:expected_response) do
              {
                "success" => false,
                "status"  => "rec_inv",
                "message" => anything
              }
            end

            include_examples "does not create the comment"
          end

          context "when the user is blocked by the answer's author" do
            before do
              answer.user.block(user)
            end

            let(:comment) { ">:3" }
            let(:expected_response) do
              {
                "success" => false,
                "status"  => "commenting_other_blocked_self",
                "message" => I18n.t("errors.commenting_other_blocked_self")
              }
            end

            include_examples "does not create the comment"
          end

          context "when the user is blocking the answer's author" do
            before do
              user.block(answer.user)
            end

            let(:comment) { "heast" }
            let(:expected_response) do
              {
                "success" => false,
                "status"  => "commenting_self_blocked_other",
                "message" => I18n.t("errors.commenting_self_blocked_other")
              }
            end

            include_examples "does not create the comment"
          end

          context "when the answer author is muting the user" do
            before do
              answer.user.mute(user)
            end

            let(:expected_response) do
              {
                "success" => true,
                "status"  => "okay",
                "message" => anything,
                "render"  => anything,
                "count"   => 1
              }
            end

            include_examples "creates the comment"

            it "does not create a notification for the author" do
              expect { subject }.to change { answer.user.notifications.count }.by(0)
            end
          end
        end

        context "when answer does not exist" do
          let(:answer_id) { "nein!" }

          let(:expected_response) do
            {
              "success" => false,
              "status"  => anything,
              "message" => anything
            }
          end

          include_examples "does not create the comment"
        end
      end

      context "when some parameters are missing" do
        let(:answer_id) { nil }
        let(:comment) { "" }

        let(:expected_response) do
          {
            "success" => false,
            "status"  => "parameter_error",
            "message" => anything
          }
        end

        include_examples "returns the expected response"
      end
    end

    context "when user is not signed in" do
      let(:answer_id) { answer.id }
      let(:comment) { "HACKED" }

      let(:expected_response) do
        {
          "success" => false,
          "status"  => "err",
          "message" => anything
        }
      end

      include_examples "returns the expected response"
    end
  end

  describe "#destroy" do
    let(:answer_user) { FactoryBot.create(:user) }
    let(:answer) { FactoryBot.create(:answer, user: answer_user) }
    let(:comment_user) { user }
    let(:comment) { FactoryBot.create(:comment, user: comment_user, answer:) }
    let(:comment_id) { comment.id }

    let(:params) do
      {
        comment: comment_id
      }
    end

    subject { delete(:destroy, params:) }

    context "when user is signed in" do
      shared_examples "deletes the comment" do
        let(:expected_response) do
          {
            "success" => true,
            "status"  => "okay",
            "message" => anything,
            "count"   => 0
          }
        end

        it "deletes the comment" do
          comment # ensure we already have it in the db
          expect { subject }.to(change { Comment.count }.by(-1))
        end

        include_examples "returns the expected response"
      end

      shared_examples "does not delete the comment" do
        let(:expected_response) do
          {
            "success" => false,
            "status"  => "nopriv",
            "message" => anything
          }
        end

        it "does not delete the comment" do
          comment # ensure we already have it in the db
          expect { subject }.not_to(change { Comment.count })
        end

        include_examples "returns the expected response"
      end

      before(:each) { sign_in(user) }

      context "when the comment exists and was made by the current user" do
        include_examples "deletes the comment"
      end

      context "when the comment exists and was not made by the current user" do
        let(:comment_user) { FactoryBot.create(:user) }

        include_examples "does not delete the comment"

        context "when the current user created the answer" do
          let(:answer_user) { user }

          include_examples "deletes the comment"
        end

        %i[moderator administrator].each do |privileged_role|
          context "when the current user is a #{privileged_role}" do
            around do |example|
              user.add_role privileged_role
              example.run
              user.remove_role privileged_role
            end

            include_examples "deletes the comment"
          end
        end
      end

      context "when the comment does not exist" do
        let(:comment_id) { "sonic_the_hedgehog" }

        let(:expected_response) do
          {
            "success" => false,
            "status"  => anything,
            "message" => anything
          }
        end

        include_examples "returns the expected response"
      end
    end

    context "when user is not signed in" do
      let(:expected_response) do
        {
          "success" => false,
          "status"  => "nopriv",
          "message" => anything
        }
      end

      include_examples "returns the expected response"
    end
  end
end