require "test_helper"

class NotificationDeliveryTest < ActiveSupport::TestCase
  setup do
    @assigner = users(:david)
    @assignee = users(:kevin)
    @card = cards(:logo)

    @card.assignments.destroy_all
    @assignee.notifications.destroy_all

    stub_dns_resolution("142.250.185.206")
    stub_web_push_pool

    @original_targets = Notification.push_targets.dup
    Notification.push_targets = []
    Notification.register_push_target(:web)
    Notification.register_push_target(push_target_with_tracking)

    # Give assignee a web push subscription
    @assignee.push_subscriptions.create!(
      endpoint: "https://fcm.googleapis.com/fcm/send/test123",
      p256dh_key: "test_key",
      auth_key: "test_auth"
    )

    Current.user = @assigner
  end

  teardown do
    Notification.push_targets = @original_targets
    @assignee.push_subscriptions.delete_all
  end

  test "card assignment creates notification and triggers push" do
    assert_difference -> { Notification.count }, 1 do
      perform_enqueued_jobs only: [ NotifyRecipientsJob, Notification::PushJob ] do
        @card.toggle_assignment(@assignee)
      end
    end

    notification = Notification.last
    assert_equal @assignee, notification.user
    assert_equal @assigner, notification.creator
    assert_equal "card_assigned", notification.source.action

    assert_push_delivered_for notification
    assert_web_push_delivered
  end

  test "card assignment notification is bundled for email delivery when bundling enabled" do
    @assignee.settings.update!(bundle_email_frequency: :every_few_hours)

    assert_difference -> { Notification.count }, 1 do
      perform_enqueued_jobs only: NotifyRecipientsJob do
        @card.toggle_assignment(@assignee)
      end
    end

    notification = @assignee.notifications.reload.last
    assert_not_nil notification, "Notification should be created for assignee"

    bundle = @assignee.notification_bundles.pending.last
    assert_not_nil bundle, "Bundle should be created when bundling is enabled"
    assert_includes bundle.notifications, notification
  end

  test "comment creates notification for card watchers and triggers push" do
    @card.watch_by(@assignee)

    assert_difference -> { Notification.count }, 1 do
      perform_enqueued_jobs only: [ NotifyRecipientsJob, Notification::PushJob ] do
        @card.comments.create!(body: "Great work on this!", creator: @assigner)
      end
    end

    notification = Notification.last
    assert_equal @assignee, notification.user
    assert_equal "comment_created", notification.source.action

    assert_push_delivered
    assert_web_push_delivered
  end

  test "mention creates notification and triggers push" do
    mention_html = ActionText::Attachment.from_attachable(@assignee).to_html

    perform_enqueued_jobs only: [ Mention::CreateJob, NotifyRecipientsJob, Notification::PushJob ] do
      @card.comments.create!(
        body: "#{mention_html} check this out",
        creator: @assigner
      )
    end

    mention_notification = @assignee.notifications.find_by(source_type: "Mention")
    assert_not_nil mention_notification

    assert_push_delivered_for mention_notification
    assert_web_push_delivered
  end

  test "system user actions do not create notifications" do
    Current.user = users(:system)

    assert_no_difference -> { Notification.count } do
      perform_enqueued_jobs only: [ NotifyRecipientsJob, Notification::PushJob ] do
        @card.toggle_assignment(@assignee)
      end
    end

    assert_no_push_delivered
    assert_no_web_push_delivered
  end

  test "notifications for inactive users are created but do not trigger push" do
    @assignee.deactivate

    assert_difference -> { Notification.count }, 1 do
      perform_enqueued_jobs only: [ NotifyRecipientsJob, Notification::PushJob ] do
        @card.toggle_assignment(@assignee)
      end
    end

    assert_no_push_delivered
    assert_no_web_push_delivered
  end

  private
    def stub_web_push_pool
      @web_push_calls = []
      web_push_pool = stub("web_push_pool")
      web_push_pool.stubs(:queue).with do |payload, subs|
        @web_push_calls << { payload: payload, subscriptions: subs }
      end

      Rails.configuration.x.stubs(:web_push_pool).returns(web_push_pool)
    end

    def push_target_with_tracking
      @push_target_calls = []
      fake_push_target = Class.new(Notification::PushTarget) do
        class << self
          attr_accessor :calls
        end

        def self.process(notification)
          calls << notification
        end
      end

      fake_push_target.tap { it.calls = @push_target_calls }
    end

    def assert_push_delivered
      assert_not_empty @push_target_calls, "Expected push to be delivered"
    end

    def assert_push_delivered_for(notification)
      assert_includes @push_target_calls, notification, "Expected push to be delivered for notification"
    end

    def assert_no_push_delivered
      assert_empty @push_target_calls, "Expected no push to be delivered"
    end

    def assert_web_push_delivered
      assert_not_empty @web_push_calls, "Expected web push to be delivered"
    end

    def assert_no_web_push_delivered
      assert_empty @web_push_calls, "Expected no web push to be delivered"
    end
end
