# frozen_string_literal: true

RSpec.describe Users::DiscourseIdController do
  let(:client_id) { SiteSetting.discourse_id_client_id }
  let(:hashed_secret) { Digest::SHA256.hexdigest(SiteSetting.discourse_id_client_secret) }
  let(:identifier) { SecureRandom.hex }
  let(:provider_name) { "discourse_id" }

  let!(:user) { Fabricate(:user) }
  let!(:user_associated_account) do
    Fabricate(:user_associated_account, user:, provider_name:, provider_uid: identifier)
  end

  before do
    SiteSetting.discourse_id_client_id = SecureRandom.hex
    SiteSetting.discourse_id_client_secret = SecureRandom.hex
    SiteSetting.enable_discourse_id = true
  end

  describe "#revoke" do
    context "with valid parameters" do
      it "revokes all auth tokens for the user" do
        UserAuthToken.generate!(user_id: user.id)
        UserAuthToken.generate!(user_id: user.id)

        expect(UserAuthToken.where(user_id: user.id).count).to eq(2)

        timestamp = Time.now.to_i
        signature =
          OpenSSL::HMAC.hexdigest(
            "sha256",
            hashed_secret,
            "#{client_id}:#{identifier}:#{timestamp}",
          )

        post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }

        expect(response.status).to eq(200)
        expect(response.parsed_body["success"]).to eq(true)

        expect(UserAuthToken.where(user_id: user.id).count).to eq(0)
      end
    end

    context "with invalid parameters" do
      it "returns 400 when signature is invalid" do
        timestamp = Time.now.to_i
        signature = SecureRandom.hex

        post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }

        expect(response.status).to eq(400)
        expect(response.parsed_body["error"]).to match(/Signature is invalid/)

        post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }

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

      it "returns 400 when timestamp is too old" do
        timestamp = Time.now.to_i - 6.minutes.to_i
        signature =
          OpenSSL::HMAC.hexdigest(
            "sha256",
            hashed_secret,
            "#{client_id}:#{identifier}:#{timestamp}",
          )

        post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }

        expect(response.status).to eq(400)
        expect(response.parsed_body["error"]).to match(/Timestamp is expired/)
      end

      it "returns 400 when user_id is not found" do
        identifier = "non_existent_user_id"
        timestamp = Time.now.to_i
        signature =
          OpenSSL::HMAC.hexdigest(
            "sha256",
            hashed_secret,
            "#{client_id}:#{identifier}:#{timestamp}",
          )

        post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }

        expect(response.status).to eq(400)
        expect(response.parsed_body["error"]).to eq("Invalid request")
      end

      it "returns 400 when client_id or client_secret is blank" do
        SiteSetting.discourse_id_client_id = ""

        timestamp = Time.now.to_i
        signature =
          OpenSSL::HMAC.hexdigest(
            "sha256",
            hashed_secret,
            "#{client_id}:#{identifier}:#{timestamp}",
          )

        post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }

        expect(response.status).to eq(400)
        expect(response.parsed_body["error"]).to eq("Invalid request")

        SiteSetting.discourse_id_client_id = SecureRandom.hex
        SiteSetting.discourse_id_client_secret = ""

        post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }

        expect(response.status).to eq(400)
        expect(response.parsed_body["error"]).to eq("Invalid request")
      end
    end

    context "when login_required is true" do
      before { SiteSetting.login_required = true }

      it "still allows anonymous revocation requests" do
        UserAuthToken.generate!(user_id: user.id)

        timestamp = Time.now.to_i
        signature =
          OpenSSL::HMAC.hexdigest(
            "sha256",
            hashed_secret,
            "#{client_id}:#{identifier}:#{timestamp}",
          )

        post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }

        expect(response.status).to eq(200)
        expect(response.parsed_body["success"]).to eq(true)
        expect(UserAuthToken.where(user_id: user.id).count).to eq(0)
      end
    end

    context "with rate limiting" do
      before { RateLimiter.enable }

      it "rate limits after 5 requests per minute for the same user_id" do
        identifier = "non_existent_user_id"
        timestamp = Time.now.to_i
        signature = SecureRandom.hex

        5.times do
          post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }
          expect(response.status).to eq(400)
        end

        post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }
        expect(response.status).to eq(429)
      end
    end
  end
end
