# -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2013-today OpenERP SA (<http://www.openerp.com>)
#
#    This program 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, either version 3 of the
#    License, or (at your option) any later version
#
#    This program 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/>
#
##############################################################################

from datetime import datetime
from dateutil import relativedelta

from openerp import tools
from openerp.tools.translate import _
from openerp.osv import osv, fields


class MassMailingCampaign(osv.Model):
    """Model of mass mailing campaigns.
    """
    _name = "mail.mass_mailing.campaign"
    _description = 'Mass Mailing Campaign'
    # number of embedded mailings in kanban view
    _kanban_mailing_nbr = 4

    def _get_statistics(self, cr, uid, ids, name, arg, context=None):
        """ Compute statistics of the mass mailing campaign """
        results = dict.fromkeys(ids, False)
        for campaign in self.browse(cr, uid, ids, context=context):
            results[campaign.id] = {
                'sent': len(campaign.statistics_ids),
                # delivered: shouldn't be: all mails - (failed + bounced) ?
                'delivered': len([stat for stat in campaign.statistics_ids if not stat.bounced]),  # stat.state == 'sent' and
                'opened': len([stat for stat in campaign.statistics_ids if stat.opened]),
                'replied': len([stat for stat in campaign.statistics_ids if stat.replied]),
                'bounced': len([stat for stat in campaign.statistics_ids if stat.bounced]),
            }
        return results

    def _get_mass_mailing_kanban_ids(self, cr, uid, ids, name, arg, context=None):
        """ Gather data about mass mailings to display them in kanban view as
        nested kanban views is not possible currently. """
        results = dict.fromkeys(ids, '')
        for campaign in self.browse(cr, uid, ids, context=context):
            mass_mailing_results = []
            for mass_mailing in campaign.mass_mailing_ids[:self._kanban_mailing_nbr]:
                mass_mailing_object = {}
                for attr in ['name', 'sent', 'delivered', 'opened', 'replied', 'bounced']:
                    mass_mailing_object[attr] = getattr(mass_mailing, attr)
                mass_mailing_results.append(mass_mailing_object)
            results[campaign.id] = mass_mailing_results
        return results

    _columns = {
        'name': fields.char(
            'Campaign Name', required=True,
        ),
        'user_id': fields.many2one(
            'res.users', 'Responsible',
            required=True,
        ),
        'mass_mailing_ids': fields.one2many(
            'mail.mass_mailing', 'mass_mailing_campaign_id',
            'Mass Mailings',
        ),
        'mass_mailing_kanban_ids': fields.function(
            _get_mass_mailing_kanban_ids,
            type='text', string='Mass Mailings (kanban data)',
            help='This field has for purpose to gather data about mass mailings '
                 'to display them in kanban view as nested kanban views is not '
                 'possible currently',
        ),
        'statistics_ids': fields.one2many(
            'mail.mail.statistics', 'mass_mailing_campaign_id',
            'Sent Emails',
        ),
        'color': fields.integer('Color Index'),
        # stat fields
        'sent': fields.function(
            _get_statistics,
            string='Sent Emails',
            type='integer', multi='_get_statistics'
        ),
        'delivered': fields.function(
            _get_statistics,
            string='Delivered',
            type='integer', multi='_get_statistics',
        ),
        'opened': fields.function(
            _get_statistics,
            string='Opened',
            type='integer', multi='_get_statistics',
        ),
        'replied': fields.function(
            _get_statistics,
            string='Replied',
            type='integer', multi='_get_statistics'
        ),
        'bounced': fields.function(
            _get_statistics,
            string='Bounced',
            type='integer', multi='_get_statistics'
        ),
    }

    _defaults = {
        'user_id': lambda self, cr, uid, ctx=None: uid,
    }

    def launch_mass_mailing_create_wizard(self, cr, uid, ids, context=None):
        ctx = dict(context)
        ctx.update({
            'default_mass_mailing_campaign_id': ids[0],
        })
        return {
            'name': _('Create a Mass Mailing for the Campaign'),
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'mail.mass_mailing.create',
            'views': [(False, 'form')],
            'view_id': False,
            'target': 'new',
            'context': ctx,
        }


class MassMailing(osv.Model):
    """ MassMailing models a wave of emails for a mass mailign campaign.
    A mass mailing is an occurence of sending emails. """

    _name = 'mail.mass_mailing'
    _description = 'Wave of sending emails'
    # number of periods for tracking mail_mail statistics
    _period_number = 6
    _order = 'date DESC'

    def __get_bar_values(self, cr, uid, id, obj, domain, read_fields, value_field, groupby_field, context=None):
        """ Generic method to generate data for bar chart values using SparklineBarWidget.
            This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field).

            :param obj: the target model (i.e. crm_lead)
            :param domain: the domain applied to the read_group
            :param list read_fields: the list of fields to read in the read_group
            :param str value_field: the field used to compute the value of the bar slice
            :param str groupby_field: the fields used to group

            :return list section_result: a list of dicts: [
                                                {   'value': (int) bar_column_value,
                                                    'tootip': (str) bar_column_tooltip,
                                                }
                                            ]
        """
        date_begin = datetime.strptime(self.browse(cr, uid, id, context=context).date, tools.DEFAULT_SERVER_DATETIME_FORMAT).date()
        section_result = [{'value': 0,
                           'tooltip': (date_begin + relativedelta.relativedelta(days=i)).strftime('%d %B %Y'),
                           } for i in range(0, self._period_number)]
        group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
        field_col_info = obj._all_columns.get(groupby_field.split(':')[0])
        pattern = tools.DEFAULT_SERVER_DATE_FORMAT if field_col_info.column._type == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT
        for group in group_obj:
            group_begin_date = datetime.strptime(group['__domain'][0][2], pattern).date()
            timedelta = relativedelta.relativedelta(group_begin_date, date_begin)
            section_result[timedelta.days] = {'value': group.get(value_field, 0), 'tooltip': group.get(groupby_field)}
        return section_result

    def _get_daily_statistics(self, cr, uid, ids, field_name, arg, context=None):
        """ Get the daily statistics of the mass mailing. This is done by a grouping
        on opened and replied fields. Using custom format in context, we obtain
        results for the next 6 days following the mass mailing date. """
        obj = self.pool['mail.mail.statistics']
        res = {}
        for id in ids:
            res[id] = {}
            date_begin = datetime.strptime(self.browse(cr, uid, id, context=context).date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
            date_end = date_begin + relativedelta.relativedelta(days=self._period_number - 1)
            date_begin_str = date_begin.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
            date_end_str = date_end.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
            domain = [('mass_mailing_id', '=', id), ('opened', '>=', date_begin_str), ('opened', '<=', date_end_str)]
            res[id]['opened_monthly'] = self.__get_bar_values(cr, uid, id, obj, domain, ['opened'], 'opened_count', 'opened:day', context=context)
            domain = [('mass_mailing_id', '=', id), ('replied', '>=', date_begin_str), ('replied', '<=', date_end_str)]
            res[id]['replied_monthly'] = self.__get_bar_values(cr, uid, id, obj, domain, ['replied'], 'replied_count', 'replied:day', context=context)
        return res

    def _get_statistics(self, cr, uid, ids, name, arg, context=None):
        """ Compute statistics of the mass mailing campaign """
        results = dict.fromkeys(ids, False)
        for mass_mailing in self.browse(cr, uid, ids, context=context):
            results[mass_mailing.id] = {
                'sent': len(mass_mailing.statistics_ids),
                'delivered': len([stat for stat in mass_mailing.statistics_ids if not stat.bounced]),  # mail.state == 'sent' and
                'opened': len([stat for stat in mass_mailing.statistics_ids if stat.opened]),
                'replied': len([stat for stat in mass_mailing.statistics_ids if stat.replied]),
                'bounced': len([stat for stat in mass_mailing.statistics_ids if stat.bounced]),
            }
        return results

    _columns = {
        'name': fields.char('Name', required=True),
        'mass_mailing_campaign_id': fields.many2one(
            'mail.mass_mailing.campaign', 'Mass Mailing Campaign',
            ondelete='cascade', required=True,
        ),
        'template_id': fields.many2one(
            'email.template', 'Email Template',
            ondelete='set null',
        ),
        'domain': fields.char('Domain'),
        'date': fields.datetime('Date'),
        'color': fields.related(
            'mass_mailing_campaign_id', 'color',
            type='integer', string='Color Index',
        ),
        # statistics data
        'statistics_ids': fields.one2many(
            'mail.mail.statistics', 'mass_mailing_id',
            'Emails Statistics',
        ),
        'sent': fields.function(
            _get_statistics,
            string='Sent Emails',
            type='integer', multi='_get_statistics'
        ),
        'delivered': fields.function(
            _get_statistics,
            string='Delivered',
            type='integer', multi='_get_statistics',
        ),
        'opened': fields.function(
            _get_statistics,
            string='Opened',
            type='integer', multi='_get_statistics',
        ),
        'replied': fields.function(
            _get_statistics,
            string='Replied',
            type='integer', multi='_get_statistics'
        ),
        'bounced': fields.function(
            _get_statistics,
            string='Bounce',
            type='integer', multi='_get_statistics'
        ),
        # monthly ratio
        'opened_monthly': fields.function(
            _get_daily_statistics,
            string='Opened',
            type='char', multi='_get_daily_statistics',
        ),
        'replied_monthly': fields.function(
            _get_daily_statistics,
            string='Replied',
            type='char', multi='_get_daily_statistics',
        ),
    }

    _defaults = {
        'date': fields.datetime.now,
    }


class MailMailStats(osv.Model):
    """ MailMailStats models the statistics collected about emails. Those statistics
    are stored in a separated model and table to avoid bloating the mail_mail table
    with statistics values. This also allows to delete emails send with mass mailing
    without loosing the statistics about them. """

    _name = 'mail.mail.statistics'
    _description = 'Email Statistics'
    _rec_name = 'message_id'
    _order = 'message_id'

    _columns = {
        'mail_mail_id': fields.integer(
            'Mail ID',
            help='ID of the related mail_mail. This field is an integer field because'
                 'the related mail_mail can be deleted separately from its statistics.'
        ),
        'message_id': fields.char(
            'Message-ID',
        ),
        'model': fields.char(
            'Document model',
        ),
        'res_id': fields.integer(
            'Document ID',
        ),
        # campaign / wave data
        'mass_mailing_id': fields.many2one(
            'mail.mass_mailing', 'Mass Mailing',
            ondelete='set null',
        ),
        'mass_mailing_campaign_id': fields.related(
            'mass_mailing_id', 'mass_mailing_campaign_id',
            type='many2one', ondelete='set null',
            relation='mail.mass_mailing.campaign',
            string='Mass Mailing Campaign',
            store=True, readonly=True,
        ),
        'template_id': fields.related(
            'mass_mailing_id', 'template_id',
            type='many2one', ondelete='set null',
            relation='email.template',
            string='Email Template',
            store=True, readonly=True,
        ),
        # Bounce and tracking
        'opened': fields.datetime(
            'Opened',
            help='Date when this email has been opened for the first time.'),
        'replied': fields.datetime(
            'Replied',
            help='Date when this email has been replied for the first time.'),
        'bounced': fields.datetime(
            'Bounced',
            help='Date when this email has bounced.'
        ),
    }

    def set_opened(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None):
        """ Set as opened """
        if not ids and mail_mail_ids:
            ids = self.search(cr, uid, [('mail_mail_id', 'in', mail_mail_ids)], context=context)
        elif not ids and mail_message_ids:
            ids = self.search(cr, uid, [('message_id', 'in', mail_message_ids)], context=context)
        else:
            ids = []
        for stat in self.browse(cr, uid, ids, context=context):
            if not stat.opened:
                self.write(cr, uid, [stat.id], {'opened': fields.datetime.now()}, context=context)
        return ids

    def set_replied(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None):
        """ Set as replied """
        if not ids and mail_mail_ids:
            ids = self.search(cr, uid, [('mail_mail_id', 'in', mail_mail_ids)], context=context)
        elif not ids and mail_message_ids:
            ids = self.search(cr, uid, [('message_id', 'in', mail_message_ids)], context=context)
        else:
            ids = []
        for stat in self.browse(cr, uid, ids, context=context):
            if not stat.replied:
                self.write(cr, uid, [stat.id], {'replied': fields.datetime.now()}, context=context)
        return ids

    def set_bounced(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None):
        """ Set as bounced """
        if not ids and mail_mail_ids:
            ids = self.search(cr, uid, [('mail_mail_id', 'in', mail_mail_ids)], context=context)
        elif not ids and mail_message_ids:
            ids = self.search(cr, uid, [('message_id', 'in', mail_message_ids)], context=context)
        else:
            ids = []
        for stat in self.browse(cr, uid, ids, context=context):
            if not stat.bounced:
                self.write(cr, uid, [stat.id], {'bounced': fields.datetime.now()}, context=context)
        return ids
