# frozen_string_literal: true

require "rails_helper"

RSpec.describe Chat::Api::ChatChannelsController do
  before do
    SiteSetting.chat_enabled = true
    SiteSetting.chat_allowed_groups = Group::AUTO_GROUPS[:everyone]
  end

  describe "#index" do
    context "as anonymous user" do
      it "returns an error" do
        get "/chat/api/channels"

        expect(response.status).to eq(403)
      end
    end

    context "as disallowed user" do
      fab!(:current_user) { Fabricate(:user) }

      before do
        SiteSetting.chat_allowed_groups = Group::AUTO_GROUPS[:staff]
        sign_in(current_user)
      end

      it "returns an error" do
        get "/chat/api/channels"

        expect(response.status).to eq(403)
      end
    end

    context "as allowed user" do
      fab!(:current_user) { Fabricate(:user) }

      before { sign_in(current_user) }

      context "with category channels" do
        context "when channel is public" do
          fab!(:channel_1) { Fabricate(:category_channel) }

          it "returns the channel" do
            get "/chat/api/channels"

            expect(response.status).to eq(200)
            expect(response.parsed_body["channels"].map { |channel| channel["id"] }).to eq(
              [channel_1.id],
            )
          end

          context "when chatable is destroyed" do
            before { channel_1.chatable.destroy! }

            it "returns nothing" do
              get "/chat/api/channels"

              expect(response.status).to eq(200)
              expect(response.parsed_body["channels"]).to be_blank
            end
          end
        end

        context "when channel has limited access" do
          fab!(:group_1) { Fabricate(:group) }
          fab!(:channel_1) { Fabricate(:private_category_channel, group: group_1) }

          context "when user has access" do
            before { group_1.add(current_user) }

            it "returns the channel" do
              get "/chat/api/channels"

              expect(response.status).to eq(200)
              expect(response.parsed_body["channels"].map { |channel| channel["id"] }).to eq(
                [channel_1.id],
              )
            end
          end

          context "when user has no access" do
            it "returns nothing" do
              get "/chat/api/channels"

              expect(response.status).to eq(200)
              expect(response.parsed_body["channels"]).to be_blank
            end

            context "when user is admin" do
              before { sign_in(Fabricate(:admin)) }

              it "returns the channels" do
                get "/chat/api/channels"

                expect(response.status).to eq(200)
                expect(response.parsed_body["channels"].map { |channel| channel["id"] }).to eq(
                  [channel_1.id],
                )
              end
            end
          end
        end
      end

      context "with direct message channels" do
        fab!(:dm_channel_1) { Fabricate(:direct_message_channel, users: [current_user]) }

        it "doesnt return direct message channels" do
          get "/chat/api/channels"
          expect(response.parsed_body["channels"]).to be_blank
        end
      end
    end
  end

  describe "#show" do
    context "when anonymous" do
      it "returns an error" do
        get "/chat/api/channels/-999"

        expect(response.status).to eq(403)
      end
    end

    context "when user cannot access channel" do
      fab!(:channel_1) { Fabricate(:private_category_channel) }

      before { sign_in(Fabricate(:user)) }

      it "returns an error" do
        get "/chat/api/channels/#{channel_1.id}"

        expect(response.status).to eq(403)
      end
    end

    context "when user can access channel" do
      fab!(:current_user) { Fabricate(:user) }

      before { sign_in(current_user) }

      context "when channel doesn’t exist" do
        it "returns an error" do
          get "/chat/api/channels/-999"

          expect(response.status).to eq(404)
        end
      end

      context "when channel exists" do
        fab!(:channel_1) { Fabricate(:category_channel) }

        it "can find channel by id" do
          get "/chat/api/channels/#{channel_1.id}"

          expect(response.status).to eq(200)
          expect(response.parsed_body["channel"]["id"]).to eq(channel_1.id)
        end
      end
    end
  end

  describe "#destroy" do
    fab!(:channel_1) { Fabricate(:category_channel) }

    context "when user is not staff" do
      fab!(:current_user) { Fabricate(:user) }

      before { sign_in(current_user) }

      it "returns an error" do
        delete "/chat/api/channels/#{channel_1.id}",
               params: {
                 channel: {
                   name_confirmation: channel_1.title(current_user),
                 },
               }

        expect(response.status).to eq(403)
      end
    end

    context "when user is admin" do
      fab!(:current_user) { Fabricate(:admin) }

      before { sign_in(current_user) }

      context "when the channel doesn’t exist" do
        before { channel_1.destroy! }

        it "returns an error" do
          delete "/chat/api/channels/#{channel_1.id}",
                 params: {
                   channel: {
                     name_confirmation: channel_1.title(current_user),
                   },
                 }

          expect(response.status).to eq(404)
        end
      end

      context "when the confirmation doesn’t match the channel name" do
        it "returns an error" do
          delete "/chat/api/channels/#{channel_1.id}",
                 params: {
                   channel: {
                     name_confirmation: channel_1.title(current_user) + "foo",
                   },
                 }

          expect(response.status).to eq(400)
        end
      end

      context "with valid params" do
        it "properly destroys the channel" do
          delete "/chat/api/channels/#{channel_1.id}",
                 params: {
                   channel: {
                     name_confirmation: channel_1.title(current_user),
                   },
                 }

          expect(response.status).to eq(200)
          expect(channel_1.reload.trashed?).to eq(true)
          expect(
            job_enqueued?(job: :chat_channel_delete, args: { chat_channel_id: channel_1.id }),
          ).to eq(true)
          expect(
            UserHistory.exists?(
              acting_user_id: current_user.id,
              action: UserHistory.actions[:custom_staff],
              custom_type: "chat_channel_delete",
            ),
          ).to eq(true)
        end

        it "generates a valid new slug to prevent collisions" do
          SiteSetting.max_topic_title_length = 20
          channel_1 = Fabricate(:chat_channel, name: "a" * SiteSetting.max_topic_title_length)
          freeze_time(DateTime.parse("2022-07-08 09:30:00"))
          old_slug = channel_1.slug

          delete(
            "/chat/api/channels/#{channel_1.id}",
            params: {
              channel: {
                name_confirmation: channel_1.title(current_user),
              },
            },
          )

          expect(response.status).to eq(200)
          expect(channel_1.reload.slug).to eq(
            "20220708-0930-#{old_slug}-deleted".truncate(
              SiteSetting.max_topic_title_length,
              omission: "",
            ),
          )
        end
      end
    end
  end

  describe "#create" do
    fab!(:admin) { Fabricate(:admin) }
    fab!(:category) { Fabricate(:category) }

    let(:params) do
      {
        channel: {
          type: category.class.name,
          chatable_id: category.id,
          name: "channel name",
          description: "My new channel",
        },
      }
    end

    before { sign_in(admin) }

    it "creates a channel associated to a category" do
      post "/chat/api/channels", params: params

      new_channel = ChatChannel.last

      expect(new_channel.name).to eq(params[:channel][:name])
      expect(new_channel.slug).to eq("channel-name")
      expect(new_channel.description).to eq(params[:channel][:description])
      expect(new_channel.chatable_type).to eq(category.class.name)
      expect(new_channel.chatable_id).to eq(category.id)
    end

    it "creates a channel using the user-provided slug" do
      new_params = params.dup
      new_params[:channel][:slug] = "wow-so-cool"
      post "/chat/api/channels", params: new_params

      new_channel = ChatChannel.last

      expect(new_channel.slug).to eq("wow-so-cool")
    end

    it "creates a channel sets auto_join_users to false by default" do
      post "/chat/api/channels", params: params

      new_channel = ChatChannel.last

      expect(new_channel.auto_join_users).to eq(false)
    end

    it "creates a channel with auto_join_users set to true" do
      params[:channel][:auto_join_users] = true
      post "/chat/api/channels", params: params

      new_channel = ChatChannel.last

      expect(new_channel.auto_join_users).to eq(true)
    end

    describe "triggers the auto-join process" do
      fab!(:chatters_group) { Fabricate(:group) }
      fab!(:user) { Fabricate(:user, last_seen_at: 15.minute.ago) }

      before do
        Jobs.run_immediately!
        Fabricate(:category_group, category: category, group: chatters_group)
        chatters_group.add(user)
      end

      it "joins the user when auto_join_users is true" do
        params[:channel][:auto_join_users] = true
        post "/chat/api/channels", params: params

        created_channel_id = response.parsed_body.dig("channel", "id")
        membership_exists =
          UserChatChannelMembership.find_by(
            user: user,
            chat_channel_id: created_channel_id,
            following: true,
          )

        expect(membership_exists).to be_present
      end

      it "doesn't join the user when auto_join_users is false" do
        params[:channel][:auto_join_users] = false
        post "/chat/api/channels", params: params

        created_channel_id = response.parsed_body.dig("channel", "id")
        membership_exists =
          UserChatChannelMembership.find_by(
            user: user,
            chat_channel_id: created_channel_id,
            following: true,
          )

        expect(membership_exists).to be_nil
      end
    end
  end

  describe "#update" do
    include_examples "channel access example", :put

    context "when user can’t edit channel" do
      fab!(:channel) { Fabricate(:category_channel) }

      before { sign_in(Fabricate(:user)) }

      it "returns a 403" do
        put "/chat/api/channels/#{channel.id}"

        expect(response.status).to eq(403)
      end
    end

    context "when user provided invalid params" do
      fab!(:channel) { Fabricate(:category_channel, user_count: 10) }

      before { sign_in(Fabricate(:admin)) }

      it "doesn’t change invalid properties" do
        put "/chat/api/channels/#{channel.id}", params: { user_count: 40 }

        expect(channel.reload.user_count).to eq(10)
      end
    end

    context "when user provided an empty name" do
      fab!(:user) { Fabricate(:admin) }
      fab!(:channel) do
        Fabricate(:category_channel, name: "something", description: "something else")
      end

      before { sign_in(user) }

      it "nullifies the field and doesn’t store an empty string" do
        put "/chat/api/channels/#{channel.id}", params: { channel: { name: "  " } }

        expect(channel.reload.name).to be_nil
      end

      it "doesn’t nullify the description" do
        put "/chat/api/channels/#{channel.id}", params: { channel: { name: "  " } }

        expect(channel.reload.description).to eq("something else")
      end
    end

    context "when user provides an empty description" do
      fab!(:user) { Fabricate(:admin) }
      fab!(:channel) do
        Fabricate(:category_channel, name: "something else", description: "something")
      end

      before { sign_in(user) }

      it "nullifies the field and doesn’t store an empty string" do
        put "/chat/api/channels/#{channel.id}", params: { channel: { description: "  " } }

        expect(channel.reload.description).to be_nil
      end

      it "doesn’t nullify the name" do
        put "/chat/api/channels/#{channel.id}", params: { channel: { description: "  " } }

        expect(channel.reload.name).to eq("something else")
      end
    end

    context "when user provides an empty slug" do
      fab!(:user) { Fabricate(:admin) }
      fab!(:channel) do
        Fabricate(:category_channel, name: "something else", description: "something")
      end

      before { sign_in(user) }

      it "does not nullify the slug" do
        put "/chat/api/channels/#{channel.id}", params: { channel: { slug: " " } }

        expect(channel.reload.slug).to eq("something-else")
      end
    end

    context "when channel is a direct message channel" do
      fab!(:user) { Fabricate(:admin) }
      fab!(:channel) { Fabricate(:direct_message_channel) }

      before { sign_in(user) }

      it "raises a 403" do
        put "/chat/api/channels/#{channel.id}"

        expect(response.status).to eq(403)
      end
    end

    context "when user provides valid params" do
      fab!(:user) { Fabricate(:admin) }
      fab!(:channel) { Fabricate(:category_channel) }

      before { sign_in(user) }

      it "sets properties" do
        put "/chat/api/channels/#{channel.id}",
            params: {
              channel: {
                name: "joffrey",
                slug: "cat-king",
                description: "cat owner",
              },
            }

        expect(channel.reload.name).to eq("joffrey")
        expect(channel.reload.slug).to eq("cat-king")
        expect(channel.reload.description).to eq("cat owner")
      end

      it "publishes an update" do
        messages =
          MessageBus.track_publish("/chat/channel-edits") do
            put "/chat/api/channels/#{channel.id}",
                params: {
                  channel: {
                    name: "A new cat overlord",
                  },
                }
          end

        message = messages[0]
        channel.reload
        expect(message.data[:chat_channel_id]).to eq(channel.id)
        expect(message.data[:name]).to eq(channel.name)
        expect(message.data[:slug]).to eq(channel.slug)
        expect(message.data[:description]).to eq(channel.description)
      end

      it "returns a valid chat channel" do
        put "/chat/api/channels/#{channel.id}", params: { channel: { name: "A new cat is born" } }

        expect(response.parsed_body["channel"]).to match_response_schema("category_chat_channel")
      end

      describe "when updating allow_channel_wide_mentions" do
        it "sets the new value" do
          put "/chat/api/channels/#{channel.id}",
              params: {
                channel: {
                  allow_channel_wide_mentions: false,
                },
              }

          expect(response.parsed_body["channel"]["allow_channel_wide_mentions"]).to eq(false)
        end
      end

      describe "Updating a channel to add users automatically" do
        it "sets the channel to auto-update users automatically" do
          put "/chat/api/channels/#{channel.id}", params: { channel: { auto_join_users: true } }

          expect(response.parsed_body["channel"]["auto_join_users"]).to eq(true)
        end

        it "tells staff members to slow down when toggling auto-update multiple times" do
          RateLimiter.enable

          put "/chat/api/channels/#{channel.id}", params: { channel: { auto_join_users: true } }
          put "/chat/api/channels/#{channel.id}", params: { channel: { auto_join_users: false } }
          put "/chat/api/channels/#{channel.id}", params: { channel: { auto_join_users: true } }

          expect(response.status).to eq(429)
        end

        describe "triggers the auto-join process" do
          fab!(:chatters_group) { Fabricate(:group) }
          fab!(:another_user) { Fabricate(:user, last_seen_at: 15.minute.ago) }

          before do
            Jobs.run_immediately!
            Fabricate(:category_group, category: channel.chatable, group: chatters_group)
            chatters_group.add(another_user)
          end

          it "joins the user when auto_join_users is true" do
            put "/chat/api/channels/#{channel.id}", params: { channel: { auto_join_users: true } }

            created_channel_id = response.parsed_body["channel"]["id"]
            membership_exists =
              UserChatChannelMembership.find_by(
                user: another_user,
                chat_channel_id: created_channel_id,
                following: true,
              )

            expect(membership_exists).to be_present
          end

          it "doesn't join the user when auto_join_users is false" do
            put "/chat/api/channels/#{channel.id}", params: { channel: { auto_join_users: false } }

            created_channel_id = response.parsed_body["channel"]["id"]

            expect(created_channel_id).to be_present

            membership_exists =
              UserChatChannelMembership.find_by(
                user: another_user,
                chat_channel_id: created_channel_id,
                following: true,
              )

            expect(membership_exists).to be_nil
          end
        end
      end
    end
  end
end
