# 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/>.
#

class DelayedMessage < ActiveRecord::Base
  include NotificationPreloader

  belongs_to :notification_policy, inverse_of: :delayed_messages
  belongs_to :notification_policy_override, inverse_of: :delayed_messages
  belongs_to :context, polymorphic:
    [
      :discussion_entry,
      :assignment,
      :sub_assignment,
      :submission_comment,
      :submission,
      :conversation_message,
      :course,
      :discussion_topic,
      :mention,
      :enrollment,
      :attachment,
      :assignment_override,
      :group_membership,
      :calendar_event,
      :wiki_page,
      :assessment_request,
      :account_user,
      :web_conference,
      :account,
      :user,
      :appointment_group,
      :collaborator,
      :account_report,
      :alert,
      :content_migration,
      :account_notification,
      {
        context_communication_channel: "CommunicationChannel",
        quiz_submission: "Quizzes::QuizSubmission",
        quiz_regrade_run: "Quizzes::QuizRegradeRun",
        master_migration: "MasterCourses::MasterMigration",
        quizzes: "Quizzes::Quiz"
      }
    ]
  belongs_to :communication_channel

  validates :summary, length: { maximum: maximum_text_length, allow_blank: true }
  validates :link, length: { maximum: maximum_text_length, allow_blank: true }
  validates :communication_channel_id, :workflow_state, presence: true

  before_save :set_send_at

  def summary=(val)
    if !val || val.length < self.class.maximum_text_length
      super
    else
      super(val[0, self.class.maximum_text_length])
    end
  end

  scope :for, lambda { |context|
    case context
    when :daily
      where(frequency: "daily")
    when :weekly
      where(frequency: "weekly")
    when Notification
      where(notification_id: context)
    when NotificationPolicy
      where(notification_policy_id: context)
    when CommunicationChannel
      where(communication_channel_id: context)
    else
      where(context:)
    end
  }

  scope :in_state, ->(state) { where(workflow_state: state.to_s) }

  include Workflow

  workflow do
    state :pending do
      event :begin_send, transitions_to: :sent do
        self.batched_at = Time.zone.now
      end
      event :cancel, transitions_to: :cancelled
    end

    state :cancelled
    state :sent
  end

  # This sets up a message and parses it internally.  Any template can
  # have these variables to build a message.  The most important one will
  # probably be delayed_messages, from which the right links and summaries
  # should be deliverable. After this is run on a list of delayed messages,
  # the regular dispatch process will take place.
  def self.summarize(delayed_message_ids)
    delayed_messages = DelayedMessage.where(id: delayed_message_ids.uniq)
    uniqs = {}
    # only include the most recent instance of each notification-context pairing
    delayed_messages.each do |m|
      uniqs[[m.context_id, m.context_type, m.notification_id]] = m
    end
    delayed_messages = uniqs.values
    delayed_messages = delayed_messages.sort_by { |dm| [dm.notification.sort_order, dm.notification.category] }
    first = delayed_messages.detect do |m|
      m.communication_channel&.active? &&
        !m.communication_channel.bouncing?
    end
    to = first&.communication_channel
    return nil unless to
    return nil if delayed_messages.empty?

    user = to.user
    context = delayed_messages.select(&:context).compact.first.try(:context)
    return nil unless context # the context for this message has already been deleted

    notification = BroadcastPolicy.notification_finder.by_name("Summaries")
    path = HostUrl.outgoing_email_address
    root_account_id = delayed_messages.first.try(:root_account_id)
    locale = user.locale || (root_account_id && Account.where(id: root_account_id).first.try(:default_locale))
    I18n.with_locale(locale) do
      message = to.messages.build(
        subject: notification.subject,
        to: to.path,
        notification_name: notification.name,
        notification:,
        from: path,
        user:
      )
      message.delayed_messages = delayed_messages
      message.context = context
      message.root_account_id = root_account_id
      message.delay_for = 0
      message.parse!
      message.save
    end
  end

  protected

  MINUTES_PER_DAY = 60 * 24
  WEEKLY_ACCOUNT_BUCKETS = 4
  MINUTES_PER_WEEKLY_ACCOUNT_BUCKET = MINUTES_PER_DAY / WEEKLY_ACCOUNT_BUCKETS

  def set_send_at
    # no cc yet = wait
    return unless communication_channel&.user
    return if send_at

    # I got tired of trying to figure out time zones in my head, and I realized
    # if we do it this way, Rails will take care of it all for us!
    if frequency == "weekly"
      target = communication_channel.user.weekly_notification_time
    else
      # Find the appropriate timezone. For weekly notifications, always use
      # Eastern. For other notifications, try and user the user's time zone,
      # defaulting to mountain. (Should be impossible to not find mountain, but
      # default to system time if necessary.)
      time_zone = communication_channel.user.time_zone || ActiveSupport::TimeZone["America/Denver"] || Time.zone
      target = time_zone.now.change(hour: 18)
      target += 1.day if target < time_zone.now
    end

    # Set the send_at value
    self.send_at = target
  end
end
