# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

import json
import logging

from datetime import datetime

from odoo import api, fields, models

_logger = logging.getLogger(__name__)


class MailMessageSchedule(models.Model):
    """ Mail message notification schedule queue.

    This model is used to store the mail messages scheduled. So we can
    delay the sending of the notifications. A scheduled date field already
    exists on the <mail.mail> but it does not allow us to delay the sending
    of the <bus.bus> notifications.
    """
    _name = 'mail.message.schedule'
    _description = 'Scheduled Messages'
    _order = 'scheduled_datetime DESC, id DESC'
    _rec_name = 'mail_message_id'

    mail_message_id = fields.Many2one(
        'mail.message', string='Message',
        ondelete='cascade', required=True)
    notification_parameters = fields.Text('Notification Parameter')
    scheduled_datetime = fields.Datetime(
        'Scheduled Send Date', required=True,
        help='Datetime at which notification should be sent.')

    @api.model_create_multi
    def create(self, vals_list):
        schedules = super().create(vals_list)
        if schedules:
            self.env.ref('mail.ir_cron_send_scheduled_message')._trigger_list(
                set(schedules.mapped('scheduled_datetime'))
            )
        return schedules

    @api.model
    def _send_notifications_cron(self):
        messages_scheduled = self.env['mail.message.schedule'].search(
            [('scheduled_datetime', '<=', datetime.utcnow())]
        )
        if messages_scheduled:
            _logger.info('Send %s scheduled messages', len(messages_scheduled))
            messages_scheduled._send_notifications()

    def force_send(self):
        """ Launch notification process independently from the expected date. """
        return self._send_notifications()

    def _send_notifications(self, default_notify_kwargs=None):
        """ Send notification for scheduled messages.

        :param dict default_notify_kwargs: optional parameters to propagate to
          ``notify_thread``. Those are default values overridden by content of
          ``notification_parameters`` field.
        """
        for model, schedules in self._group_by_model().items():
            if model:
                records = self.env[model].browse(schedules.mapped('mail_message_id.res_id'))
            else:
                records = [self.env['mail.thread']] * len(schedules)

            for record, schedule in zip(records, schedules):
                notify_kwargs = dict(default_notify_kwargs or {}, skip_existing=True)
                try:
                    schedule_notify_kwargs = json.loads(schedule.notification_parameters)
                except Exception:
                    pass
                else:
                    schedule_notify_kwargs.pop('scheduled_date', None)
                    notify_kwargs.update(schedule_notify_kwargs)

                record._notify_thread(schedule.mail_message_id, msg_vals=False, **notify_kwargs)

        self.unlink()
        return True

    @api.model
    def _send_message_notifications(self, messages, default_notify_kwargs=None):
        """ Send scheduled notification for given messages.

        :param <mail.message> messages: scheduled sending related to those messages
          will be sent now;
        :param dict default_notify_kwargs: optional parameters to propagate to
          ``notify_thread``. Those are default values overridden by content of
          ``notification_parameters`` field.

        :return bool: False if no schedule has been found, True otherwise
        """
        messages_scheduled = self.search(
            [('mail_message_id', 'in', messages.ids)]
        )
        if not messages_scheduled:
            return False

        messages_scheduled._send_notifications(default_notify_kwargs=default_notify_kwargs)
        return True

    @api.model
    def _update_message_scheduled_datetime(self, messages, new_datetime):
        """ Update scheduled datetime for scheduled sending related to messages.

        :param <mail.message> messages: scheduled sending related to those messages
          will be updated. Missing one are skipped;
        :param datetime new_datetime: new datetime for sending. New triggers
          are created based on it;

        :return bool: False if no schedule has been found, True otherwise
        """
        messages_scheduled = self.search(
            [('mail_message_id', 'in', messages.ids)]
        )
        if not messages_scheduled:
            return False

        messages_scheduled.scheduled_datetime = new_datetime
        self.env.ref('mail.ir_cron_send_scheduled_message')._trigger(new_datetime)
        return True

    def _group_by_model(self):
        grouped = {}
        for schedule in self:
            model = schedule.mail_message_id.model if schedule.mail_message_id.model and schedule.mail_message_id.res_id else False
            if model not in grouped:
                grouped[model] = schedule
            else:
                grouped[model] += schedule
        return grouped
