# frozen_string_literal: true

#
# Copyright (C) 2018 - 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/>.

class ObserverAlertThreshold < ActiveRecord::Base
  belongs_to :student, :class_name => 'User', inverse_of: :as_student_observer_alert_thresholds, :foreign_key => :user_id
  belongs_to :observer, :class_name => 'User', inverse_of: :as_observer_observer_alert_thresholds
  has_many :observer_alerts, :inverse_of => :observer_alert_threshold

  ALERT_TYPES_WITH_THRESHOLD = %w(
    assignment_grade_high
    assignment_grade_low
    course_grade_high
    course_grade_low
  ).freeze

  ALERT_TYPES_WITHOUT_THRESHOLD = %w(
    assignment_missing
    course_announcement
    institution_announcement
  ).freeze

  ALERT_TYPES = (ALERT_TYPES_WITH_THRESHOLD | ALERT_TYPES_WITHOUT_THRESHOLD).freeze

  validates :alert_type, inclusion: { in: ALERT_TYPES }
  validates :user_id, :observer_id, :alert_type, presence: true
  validates :alert_type, uniqueness: { scope: [:user_id, :observer_id] }
  validates :threshold, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 100 }, allow_nil: true
  validate :validate_threshold_type
  validate :validate_threshold_low_high
  validate :validate_users_link

  scope :active, -> { where.not(workflow_state: 'deleted') }

  def validate_users_link
    unless users_are_still_linked?
      errors.add(:observer_id, "Observer must be linked to Student")
    end
  end

  def users_are_still_linked?
    return true if observer.as_observer_observation_links.active.where(student: student).exists?
    return true if observer.enrollments.active.where(associated_user: student).shard(observer).exists?
    false
  end

  # Validates alert types that require a treshold
  # Also enforces _not_ passing a threshold if one is not required
  def validate_threshold_type
    # If a threshold is provided, there are only 4 applicable types of alert
    if self.threshold
      unless ALERT_TYPES_WITH_THRESHOLD.include? self.alert_type
        errors.add(:threshold, "Threshold is only applicable to the following alert types: #{ALERT_TYPES_WITH_THRESHOLD.join(', ')}")
      end
    else
      unless ALERT_TYPES_WITHOUT_THRESHOLD.include? self.alert_type
        errors.add(:threshold, "Threshold is required for the provided alert_type.")
      end
    end
  end

  # Validates the highs and lows of a single alert type, enforcing that a high threshold cannot be lower than a low threshold, or vica versa
  # For example:
  # If the user sets assignment_grade_high to be 40, and then tries to set assignment_grade_low to 50, that would be rejected.
  # On the flip side, if assignment_grade_low is set to 50, and then assignment_grade_high is set to 20, will be rejected
  def validate_threshold_low_high
    if ALERT_TYPES_WITH_THRESHOLD.include? self.alert_type
      opposite_type = if self.alert_type.include? 'high'
        self.alert_type.gsub('high', 'low')
      else
        self.alert_type.gsub('low', 'high')
      end

      opposite = observer.as_observer_observer_alert_thresholds.where(alert_type: opposite_type)
      if opposite.any?
        if (self.alert_type.include? 'high') && (self.threshold.to_i <= opposite.first.threshold.to_i)
          errors.add(:threshold, 'You cannot set a high threshold that is lower or equal to a previously set low threshold.')
        elsif (self.alert_type.include? 'low') && (self.threshold.to_i >= opposite.first.threshold.to_i)
          errors.add(:threshold, 'You cannot set a low threshold that is higher or equal to a previously set high threshold.')
        end
      end
    end
  end

  def destroy
    self.workflow_state = 'deleted'
    self.save!
  end

  def did_pass_threshold(previous_value, new_value)
    t = self.threshold.to_i
    if self.alert_type.include? 'high'
      return (previous_value.nil? || previous_value < t) && (!new_value.nil? && new_value > t)
    elsif self.alert_type.include? 'low'
      return (previous_value.nil? || previous_value > t) && (!new_value.nil? && new_value < t)
    end
  end
end
