# frozen_string_literal: true

RSpec.describe PushNotificationPusher do
  it "returns badges url by default" do
    expect(PushNotificationPusher.get_badge).to eq("/assets/push-notifications/discourse.png")
  end

  it "returns custom badges url" do
    upload = Fabricate(:upload)
    SiteSetting.push_notifications_icon = upload

    expect(PushNotificationPusher.get_badge).to eq(UrlHelper.absolute(upload.url))
  end

  context "with user" do
    fab!(:user)
    let(:topic_title) { "Topic" }
    let(:post_url) { "https://example.com/t/1/2" }
    let(:username) { "system" }

    def create_subscription
      data = <<~JSON
      {
        "endpoint": "endpoint",
        "keys": {
          "p256dh": "p256dh",
          "auth": "auth"
        }
      }
      JSON
      PushSubscription.create!(user_id: user.id, data: data)
    end

    def execute_push(notification_type: 1, post_number: 1)
      PushNotificationPusher.push(
        user,
        {
          topic_title: topic_title,
          username: username,
          excerpt: "description",
          topic_id: 1,
          post_url: post_url,
          notification_type: notification_type,
          post_number: post_number,
        },
      )
    end

    it "correctly guesses an image if missing" do
      message = execute_push(notification_type: -1)
      expect(message[:icon]).to eq("/assets/push-notifications/discourse.png")
    end

    it "correctly finds image if exists" do
      message = execute_push(notification_type: 1)
      expect(message[:icon]).to eq("/assets/push-notifications/mentioned.png")
    end

    it "sends notification in user's locale" do
      SiteSetting.allow_user_locale = true
      user.update!(locale: "pt_BR")

      TranslationOverride.upsert!(
        "pt_BR",
        "discourse_push_notifications.popup.mentioned",
        "pt_BR notification",
      )

      WebPush
        .expects(:payload_send)
        .with { |*args| JSON.parse(args.first[:message])["title"] == "pt_BR notification" }
        .once

      create_subscription
      execute_push
    end

    it "triggers a DiscourseEvent with user and message arguments" do
      WebPush.expects(:payload_send)
      create_subscription
      pn_sent_event = DiscourseEvent.track_events { message = execute_push }.first

      expect(pn_sent_event[:event_name]).to eq(:push_notification_sent)
      expect(pn_sent_event[:params].first).to eq(user)
      expect(pn_sent_event[:params].second[:url]).to eq(post_url)
    end

    it "deletes subscriptions which are erroring regularly" do
      start = freeze_time

      sub = create_subscription

      response = Struct.new(:body, :inspect, :message).new("test", "test", "failed")
      error = WebPush::ResponseError.new(response, "localhost")

      WebPush.expects(:payload_send).raises(error).times(4)

      # 3 failures in more than 24 hours
      3.times do
        execute_push

        freeze_time 1.minute.from_now
      end

      sub.reload
      expect(sub.error_count).to eq(3)
      expect(sub.first_error_at).to eq_time(start)

      freeze_time(2.days.from_now)

      execute_push

      expect(PushSubscription.where(id: sub.id).exists?).to eq(false)
    end

    it "deletes invalid subscriptions during send" do
      missing_endpoint =
        PushSubscription.create!(
          user_id: user.id,
          data: { p256dh: "public ECDH key", keys: { auth: "private ECDH key" } }.to_json,
        )

      missing_p256dh =
        PushSubscription.create!(
          user_id: user.id,
          data: { endpoint: "endpoint 1", keys: { auth: "private ECDH key" } }.to_json,
        )

      missing_auth =
        PushSubscription.create!(
          user_id: user.id,
          data: { endpoint: "endpoint 2", keys: { p256dh: "public ECDH key" } }.to_json,
        )

      valid_subscription =
        PushSubscription.create!(
          user_id: user.id,
          data: {
            endpoint: "endpoint 3",
            keys: {
              p256dh: "public ECDH key",
              auth: "private ECDH key",
            },
          }.to_json,
        )

      expect(PushSubscription.where(user_id: user.id)).to contain_exactly(
        missing_endpoint,
        missing_p256dh,
        missing_auth,
        valid_subscription,
      )
      WebPush
        .expects(:payload_send)
        .with(
          has_entries(endpoint: "endpoint 3", p256dh: "public ECDH key", auth: "private ECDH key"),
        )
        .once

      execute_push

      expect(PushSubscription.where(user_id: user.id)).to contain_exactly(valid_subscription)
    end

    it "handles timeouts" do
      WebPush.expects(:payload_send).raises(Net::ReadTimeout.new)
      subscription = create_subscription

      expect { execute_push }.to_not raise_exception

      subscription.reload
      expect(subscription.error_count).to eq(1)
    end

    describe "`watching_category_or_tag` notifications" do
      it "Uses the 'watching_first_post' translation when new topic was created" do
        message =
          execute_push(
            notification_type: Notification.types[:watching_category_or_tag],
            post_number: 1,
          )

        expect(message[:title]).to eq(
          I18n.t(
            "discourse_push_notifications.popup.watching_first_post",
            site_title: SiteSetting.title,
            topic: topic_title,
            username: username,
          ),
        )
      end

      it "Uses the 'posted' translation when new post was created" do
        message =
          execute_push(
            notification_type: Notification.types[:watching_category_or_tag],
            post_number: 2,
          )

        expect(message[:title]).to eq(
          I18n.t(
            "discourse_push_notifications.popup.posted",
            site_title: SiteSetting.title,
            topic: topic_title,
            username: username,
          ),
        )
      end
    end

    describe "push_notification_pusher_title_payload modifier" do
      let(:modifier_block) do
        Proc.new do |payload|
          payload[:username] = "super_hijacked"
          payload
        end
      end
      it "Allows modifications to the payload passed to the translation" do
        plugin_instance = Plugin::Instance.new
        plugin_instance.register_modifier(:push_notification_pusher_title_payload, &modifier_block)

        message = execute_push(notification_type: Notification.types[:mentioned], post_number: 2)

        expect(message[:title]).to eq(
          I18n.t(
            "discourse_push_notifications.popup.mentioned",
            site_title: SiteSetting.title,
            topic: topic_title,
            username: "super_hijacked",
          ),
        )
      ensure
        DiscoursePluginRegistry.unregister_modifier(
          plugin_instance,
          :push_notification_pusher_title_payload,
          &modifier_block
        )
      end
    end
  end
end