# This file is part of Tryton.  The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

from dateutil.relativedelta import relativedelta
from sql import Literal
from sql.aggregate import Count, Min
from sql.conditionals import NullIf
from sql.functions import DateTrunc, Round

from trytond.model import ModelSQL, ModelView, fields
from trytond.pool import Pool
from trytond.pyson import Eval, If
from trytond.transaction import Transaction


class Context(ModelView):
    __name__ = 'marketing.automation.reporting.context'

    from_date = fields.Date("From Date",
        domain=[
            If(Eval('to_date') & Eval('from_date'),
                ('from_date', '<=', Eval('to_date')),
                ()),
            ])
    to_date = fields.Date("To Date",
        domain=[
            If(Eval('from_date') & Eval('to_date'),
                ('to_date', '>=', Eval('from_date')),
                ()),
            ])
    period = fields.Selection([
            ('year', "Year"),
            ('month', "Month"),
            ('day', "Day"),
            ], "Period", required=True)

    @classmethod
    def default_from_date(cls):
        pool = Pool()
        Date = pool.get('ir.date')
        context = Transaction().context
        if 'from_date' in context:
            return context['from_date']
        return Date.today() - relativedelta(years=1)

    @classmethod
    def default_to_date(cls):
        pool = Pool()
        Date = pool.get('ir.date')
        context = Transaction().context
        if 'to_date' in context:
            return context['to_date']
        return Date.today()

    @classmethod
    def default_period(cls):
        return Transaction().context.get('period', 'month')


class Abstract(ModelSQL, ModelView):

    date = fields.Date("Date")

    @classmethod
    def table_query(cls):
        from_item, tables, withs = cls._joins()
        return from_item.select(*cls._columns(tables, withs),
            where=cls._where(tables, withs),
            group_by=cls._group_by(tables, withs),
            with_=withs.values())

    @classmethod
    def _columns(cls, tables, withs):
        return [
            cls._column_id(tables, withs).as_('id'),
            cls._column_date(tables, withs).as_('date'),
            ]


class Scenario(Abstract):
    __name__ = 'marketing.automation.reporting.scenario'

    scenario = fields.Many2One(
        'marketing.automation.scenario', "Scenario")
    record_count = fields.Integer("Records")
    record_count_blocked = fields.Integer("Records Blocked")
    block_rate = fields.Float("Block Rate")

    @classmethod
    def _joins(cls):
        pool = Pool()
        Record = pool.get('marketing.automation.record')
        tables = {}
        tables['record'] = record = Record.__table__()
        withs = {}
        return record, tables, withs

    @classmethod
    def _columns(cls, tables, withs):
        record = tables['record']

        record_count = Count()
        record_count_blocked = Count(filter_=record.blocked)

        return super()._columns(tables, withs) + [
            record.scenario.as_('scenario'),
            record_count.as_('record_count'),
            record_count_blocked.as_('record_count_blocked'),
            cls.block_rate.sql_cast(
                Round(record_count_blocked / NullIf(record_count, 0), 2)).as_(
                'block_rate'),
            ]

    @classmethod
    def _column_id(cls, tables, withs):
        record = tables['record']
        return Min(record.id)

    @classmethod
    def _column_date(cls, tables, withs):
        context = Transaction().context
        record = tables['record']

        date = DateTrunc(context.get('period', 'month'), record.create_date)
        return cls.date.sql_cast(date)

    @classmethod
    def _group_by(cls, tables, withs):
        record = tables['record']
        return [record.scenario, cls._column_date(tables, withs).as_('date')]

    @classmethod
    def _where(cls, tables, withs):
        context = Transaction().context
        record = tables['record']

        where = Literal(True)
        from_date = context.get('from_date')
        if from_date:
            where &= record.create_date >= from_date
        to_date = context.get('to_date')
        if to_date:
            where &= record.create_date <= to_date
        return where


class Activity(Abstract):
    __name__ = 'marketing.automation.reporting.activity'

    activity = fields.Many2One(
        'marketing.automation.activity', "Activity")
    activity_action = fields.Function(
        fields.Selection('get_activity_actions', "Activity Action"),
        'on_change_with_activity_action')
    record_count = fields.Integer("Records")
    email_opened = fields.Integer(
        "Emails Opened",
        states={
            'invisible': Eval('activity_action') != 'send_email',
            })
    email_clicked = fields.Integer(
        "Emails Clicked",
        states={
            'invisible': Eval('activity_action') != 'send_email',
            })
    email_open_rate = fields.Float(
        "Email Open Rate",
        states={
            'invisible': Eval('activity_action') != 'send_email',
            })
    email_click_rate = fields.Float(
        "Email Click Rate",
        states={
            'invisible': Eval('activity_action') != 'send_email',
            })
    email_click_through_rate = fields.Float(
        "Email Click-Through Rate",
        states={
            'invisible': Eval('activity_action') != 'send_email',
            })

    @classmethod
    def get_activity_actions(cls):
        pool = Pool()
        Activity = pool.get('marketing.automation.activity')
        return Activity.fields_get(['action'])['action']['selection']

    @fields.depends('activity')
    def on_change_with_activity_action(self, name=None):
        if self.activity:
            return self.activity.action

    @classmethod
    def _joins(cls):
        pool = Pool()
        RecordActivity = pool.get('marketing.automation.record.activity')
        tables = {}
        tables['record_activity'] = record = RecordActivity.__table__()
        withs = {}
        return record, tables, withs

    @classmethod
    def _columns(cls, tables, withs):
        record_activity = tables['record_activity']

        record_count = Count(filter_=record_activity.state == 'done')
        email_opened = Count(filter_=record_activity.email_opened)
        email_clicked = Count(filter_=record_activity.email_clicked)

        return super()._columns(tables, withs) + [
            record_activity.activity.as_('activity'),
            record_count.as_('record_count'),
            email_opened.as_('email_opened'),
            email_clicked.as_('email_clicked'),
            cls.email_open_rate.sql_cast(
                Round(email_opened / NullIf(record_count, 0), 2)).as_(
                    'email_open_rate'),
            cls.email_click_rate.sql_cast(
                Round(email_clicked / NullIf(record_count, 0), 2)).as_(
                    'email_click_rate'),
            cls.email_click_through_rate.sql_cast(
                Round(email_clicked / NullIf(email_opened, 0), 2)).as_(
                'email_click_through_rate'),
            ]

    @classmethod
    def _column_id(cls, tables, withs):
        record_activity = tables['record_activity']
        return Min(record_activity.id)

    @classmethod
    def _column_date(cls, tables, withs):
        context = Transaction().context
        record_activity = tables['record_activity']

        date = DateTrunc(context.get('period', 'month'), record_activity.at)
        return cls.date.sql_cast(date)

    @classmethod
    def _group_by(cls, tables, withs):
        record_activity = tables['record_activity']
        return [
            record_activity.activity,
            cls._column_date(tables, withs).as_('date')]

    @classmethod
    def _where(cls, tables, withs):
        context = Transaction().context
        record_activity = tables['record_activity']

        where = Literal(True)
        from_date = context.get('from_date')
        if from_date:
            where &= record_activity.at >= from_date
        to_date = context.get('to_date')
        if to_date:
            where &= record_activity.at <= to_date
        return where
