# frozen_string_literal: true

#
# Copyright (C) 2011 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
require File.expand_path(File.dirname(__FILE__) + '/../sharding_spec_helper')

describe NotificationPolicy do
  it "should create a new instance given valid attributes" do
    notification_policy_model
  end

  context "channels" do
    before(:once) do
      @course = factory_with_protected_attributes(Course, :name => "test course", :workflow_state => "available")
      @student = factory_with_protected_attributes(User, :name => "student", :workflow_state => "registered")
      e = @course.enroll_student(@student)
      e.accept!
      Notification.all.each{|n| n.destroy }
      Notification.reset_cache!
      @notif = Notification.create!(:name => "Assignment Created", :subject => "Test", :category => 'TestNever')
    end

    it "should cause message dispatch to specified channel on triggered policies" do
      communication_channel(@student, {username: 'default@example.com', active_cc: true})
      communication_channel(@student, {username: 'secondary@example.com', active_cc: true})
      @policy = NotificationPolicy.create(:notification => @notif, :communication_channel => @cc, :frequency => "immediately")
      @assignment = @course.assignments.create!(:title => "test assignment")
      expect(@assignment.messages_sent).to be_include("Assignment Created")
      m = @assignment.messages_sent["Assignment Created"].find{|message| message.to == "default@example.com"}
      expect(m).to be_nil
      m = @assignment.messages_sent["Assignment Created"].find{|message| message.to == "secondary@example.com"}
      expect(m).not_to be_nil
    end

    it "should prevent message dispatches if set to 'never' on triggered policies" do
      communication_channel(@student, {username: 'secondary@example.com', active_cc: true})
      @policy = NotificationPolicy.create(:notification => @notif, :communication_channel => @cc, :frequency => "never")
      @assignment = @course.assignments.create!(:title => "test assignment")
      m = @assignment.messages_sent["Assignment Created"].find{|message| message.to == "default@example.com"}
      expect(m).to be_nil
      m = @assignment.messages_sent["Assignment Created"].find{|message| message.to == "secondary@example.com"}
      expect(m).to be_nil
    end

    it "should prevent message dispatches if no policy setting exists" do
      communication_channel(@student, {username: 'secondary@example.com', active_cc: true})
      NotificationPolicy.where(:notification_id => @notif, :communication_channel_id => @cc).delete_all
      @assignment = @course.assignments.create!(:title => "test assignment")
      m = @assignment.messages_sent["Assignment Created"].find{|message| message.to == "default@example.com"}
      expect(m).to be_nil
      m = @assignment.messages_sent["Assignment Created"].find{|message| message.to == "secondary@example.com"}
      expect(m).to be_nil
    end
  end

  it "should pass 'data' to the message" do
    Notification.create! :name => "Hello",
                         :subject => "Hello",
                         :category => "TestImmediately"
    allow_any_instance_of(Message).to receive(:get_template).and_return("here's a free id <%= data.course_id %>")
    class DataTest < ActiveRecord::Base
      self.table_name = :courses

      has_a_broadcast_policy
      set_broadcast_policy do
        dispatch :hello
        to {
          u = User.create
          u.communication_channels.build(
            :path => 'blarg@example.com',
            :path_type => 'email'
          ) { |cc| cc.workflow_state = 'active' }
          u.save!
          u.register
          u
        }
        whenever { true }
        data { {course_id: 'this is a real course_id', root_account_id: Account.default.id} }
      end
      def root_account
        Account.default
      end
    end
    dt = DataTest.new(account_id: Account.default.id,
                      root_account_id: Account.default.id,
                      enrollment_term_id: Account.default.default_enrollment_term.id,
                      workflow_state: 'created')
    dt.save!
    msg = dt.messages_sent["Hello"].find { |m| m.to == "blarg@example.com" }
    expect(msg).not_to be_nil
    expect(msg.body).to include "this is a real course_id"
  end

  context "named scopes" do
    it "should have a named scope for users" do
      user_with_pseudonym(:active_all => 1)
      notification_policy_model(:communication_channel => @cc)
      expect(NotificationPolicy.for(@user)).to eq [@notification_policy]
    end

    it "should have a named scope for notifications" do
      notification_model
      notification_policy_model(:notification => @notification)
      expect(NotificationPolicy.for(@notification)).to eq [@notification_policy]
    end

    it "should not slow down from other kinds of input on the *for* named scope" do
      notification_policy_model
      expect(NotificationPolicy.for(:anything_else)).to eq NotificationPolicy.all
    end

    context "by" do
      before :once do
        user_with_pseudonym(:active_all => 1)
        @n1 = notification_policy_model(:frequency => 'immediately', :communication_channel => @cc, notification: notification_model(name: 'N1'))
        @n2 = notification_policy_model(:frequency => 'daily', :communication_channel => @cc, notification: notification_model(name: 'N2'))
        @n3 = notification_policy_model(:frequency => 'weekly', :communication_channel => @cc, notification: notification_model(name: 'N3'))
        @n4 = notification_policy_model(:frequency => 'never', :communication_channel => @cc, notification: notification_model(name: 'N4'))
      end

      it "should have a scope to differentiate by frequency" do
        expect(NotificationPolicy.by_frequency(:immediately)).to eq [@n1]
        expect(NotificationPolicy.by_frequency(:daily)).to eq [@n2]
        expect(NotificationPolicy.by_frequency(:weekly)).to eq [@n3]
        expect(NotificationPolicy.by_frequency(:never)).to eq [@n4]
      end

      it "should be able to differentiate by several frequencies at once" do
        expect(NotificationPolicy.by_frequency([:immediately, :daily])).to be_include(@n1)
        expect(NotificationPolicy.by_frequency([:immediately, :daily])).to be_include(@n2)
      end

      it "should be able to combine an array of frequencies with a for scope" do
        expect(NotificationPolicy.for(@user).by_frequency([:daily, :weekly])).to be_include(@n2)
        expect(NotificationPolicy.for(@user).by_frequency([:daily, :weekly])).to be_include(@n3)
        expect(NotificationPolicy.for(@user).by_frequency([:daily, :weekly])).not_to be_include(@n1)
      end
    end

    it "should find all daily and weekly policies for the user, communication_channel, and notification" do
      user_model
      communication_channel_model
      notification_model

      NotificationPolicy.delete_all

      trifecta_opts = {
        :communication_channel => @communication_channel,
        :notification => @notification
      }

      n1 = notification_policy_model

      policies = NotificationPolicy.for(@notification).for(@user).for(@communication_channel).by_frequency([:daily, :weekly])
      expect(policies).to eq []

      n1.update_attribute(:frequency, 'never')
      policies = NotificationPolicy.for(@notification).for(@user).for(@communication_channel).by_frequency([:daily, :weekly])
      expect(policies).to eq []

      n1.update_attribute(:frequency, 'daily')
      policies = NotificationPolicy.for(@notification).for(@user).for(@communication_channel).by_frequency([:daily, :weekly])
      expect(policies).to eq [n1]

      n1.update_attribute(:frequency, 'weekly')
      policies = NotificationPolicy.for(@notification).for(@user).for(@communication_channel).by_frequency([:daily, :weekly])
      expect(policies).to eq [n1]
    end

  end

  describe "setup_for" do
    it "should not fail when params does not include a user, and the account doesn't allow scores in e-mails" do
      user_model
      communication_channel_model
      notify1 = notification_model(:name => 'Setting 1', :category => 'MultiCategory')
      params = { :channel_id => @communication_channel.id }
      params[:root_account] = Account.default
      params[:root_account].settings[:allow_sending_scores_in_emails] = false
      NotificationPolicy.setup_for(@user, params)
    end

    it "should update send_observed_names_in_notifications when included" do
      user_model
      communication_channel_model
      params = { :channel_id => @communication_channel.id }
      params[:root_account] = Account.default
      params[:user] = { :send_observed_names_in_notifications => 'true' }
      NotificationPolicy.setup_for(@user, params)
      expect(@user.send_observed_names_in_notifications?).to eq true
      params[:user] = { :send_observed_names_in_notifications => 'false' }
      NotificationPolicy.setup_for(@user, params)
      expect(@user.send_observed_names_in_notifications?).to eq false

      # Verify KNO-298
      params[:root_account].settings[:allow_sending_scores_in_emails] = false
      params[:user] = { :send_observed_names_in_notifications => 'true' }
      NotificationPolicy.setup_for(@user, params)
      expect(@user.send_observed_names_in_notifications?).to eq true
    end

    it "should set all notification entries within the same category" do
      user_model
      communication_channel_model
      notify1 = notification_model(:name => 'Setting 1', :category => 'MultiCategory')
      notify2 = notification_model(:name => 'Setting 2', :category => 'MultiCategory')

      NotificationPolicy.delete_all

      trifecta_opts = {
        :communication_channel => @communication_channel,
        :frequency => Notification::FREQ_NEVER
      }
      n1 = notification_policy_model(trifecta_opts.merge(:notification => notify1) )
      n2 = notification_policy_model(trifecta_opts.merge(:notification => notify2) )
      params = {:category => 'multi_category', :channel_id => @communication_channel.id, :frequency => Notification::FREQ_IMMEDIATELY}
      NotificationPolicy.setup_for(@user, params)
      n1.reload; n2.reload
      expect(n1.frequency).to eq Notification::FREQ_IMMEDIATELY
      expect(n2.frequency).to eq Notification::FREQ_IMMEDIATELY
    end

    it "does not modify another user's preferences" do
      notification_model
      hax0r = user_with_communication_channel
      user_with_communication_channel
      expect {
        NotificationPolicy.setup_for(hax0r, :channel_id => @cc.id, :frequency => Notification::FREQ_IMMEDIATELY, :category => 'test_immediately')
      }.to raise_error(ActiveRecord::RecordNotFound)
      expect(@user.notification_policies.any?).to eq false
    end

    context "sharding" do
      specs_require_sharding

      it "should create records on the correct shard" do
        user_with_pseudonym(active_all: true)
        NotificationPolicy.delete_all
        notification_model
        @shard1.activate do
          expect(@user.notification_policies.scope.exists?).to be_falsey
          NotificationPolicy.setup_for(@user, channel_id: @cc.id, frequency: Notification::FREQ_IMMEDIATELY, category: 'test_immediately')
          expect(@user.notification_policies.scope.exists?).to be_truthy
        end
      end
    end
  end

  describe "setup_with_default_policies" do
    before :once do
      @user = User.create!
      @communication_channel = communication_channel(@user, {username: 'email@example.com'})
      @announcement = notification_model(:name => 'Setting 1', :category => 'Announcement')
    end

    before :each do
      allow(Notification).to receive(:all).and_return([@notification])
    end

    it "should create default NotificationPolicy entries if missing" do
      # Ensure no existing policies
      NotificationPolicy.delete_all

      policies = NotificationPolicy.setup_with_default_policies(@user, [@announcement])
      expect(policies.length).to eq 1
      expect(policies.first.frequency).to eq @announcement.default_frequency
    end

    it "should not overwrite an existing setting with a default" do
      # Create an existing policy entry
      NotificationPolicy.delete_all
      n1 = notification_policy_model({:communication_channel => @communication_channel,
                                      :notification => @announcement,
                                      :frequency => Notification::FREQ_NEVER})

      expect(@announcement.default_frequency).not_to eq Notification::FREQ_NEVER # verify that it differs from the default
      policies = NotificationPolicy.setup_with_default_policies(@user, [@announcement])
      expect(policies.length).to eq 1
      expect(policies.first.frequency).to eq Notification::FREQ_NEVER
    end

    it "should not set defaults on secondary communication channel" do
      NotificationPolicy.delete_all
      # Setup the second channel (higher position)
      primary_channel   = @user.communication_channel
      secondary_channel = communication_channel_model(:path => 'secondary@example.com')
      # start out with 0 on primary and secondary
      expect(primary_channel.notification_policies.count).to eq 0
      expect(secondary_channel.notification_policies.count).to eq 0
      # Load data
      NotificationPolicy.setup_with_default_policies(@user, [@announcement])
      # Primary should have 1 created and secondary should be left alone.
      expect(primary_channel.notification_policies.count).to eq 1
      expect(secondary_channel.notification_policies.count).to eq 0
    end

    it "should not pull defaults from non-default channels" do
      NotificationPolicy.delete_all
      # Setup the second channel (higher position)
      primary_channel   = @user.communication_channel
      secondary_channel = communication_channel_model(:path => 'secondary@example.com')
      secondary_channel.notification_policies.create!(:notification => @notification, :frequency => Notification::FREQ_NEVER)
      NotificationPolicy.setup_with_default_policies(@user, [@announcement])
      # Primary should have 1 created and secondary should be left alone.
      expect(primary_channel.reload.notification_policies.count).to eq 1
      expect(secondary_channel.reload.notification_policies.count).to eq 1
    end

    it "should not error if no channel exists" do
      NotificationPolicy.delete_all
      CommunicationChannel.delete_all
      expect { NotificationPolicy.setup_with_default_policies(@user, [@announcement])}.not_to raise_error
    end

    context "across shards" do
      specs_require_sharding

      it "should find user categories accross shards" do
        @shard1.activate {
          @shard_user = user_model
          @channel = communication_channel_model(:user => @shard_user)
          NotificationPolicy.delete_all
          @policy = @channel.notification_policies.create!(:notification => @notification, :frequency => Notification::FREQ_NEVER)
          NotificationPolicy.setup_with_default_policies(@shard_user, [@announcement])
          expect(@policy.reload.frequency).to eq Notification::FREQ_NEVER
        }
      end
    end
  end

  context 'find_all_for' do
    it 'should only return course type notification policies if provided a course context type' do
      student = factory_with_protected_attributes(User, :name => "student", :workflow_state => "registered")
      channel = communication_channel(student, {username: 'default@example.com', active_cc: true})

      course_type_notification = Notification.create!(:name => "Course Type", :subject => "Test", :category => 'Due Date')
      notification = Notification.create!(:name => "Panda Express", :subject => "Test", :category => 'Whatever')

      NotificationPolicy.create(:notification => course_type_notification, :communication_channel => channel, :frequency => "immediately")
      NotificationPolicy.create(:notification => notification, :communication_channel => channel, :frequency => "daily")
      NotificationPolicy.create(:communication_channel => channel, :frequency => "daily")

      policies = NotificationPolicy.find_all_for(channel, context_type: 'Course')
      expect(policies.count).to eq 1
      expect(policies.first.notification.name).to eq course_type_notification.name
    end
  end
end
