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

from dateutil.relativedelta import relativedelta

from odoo import _, api, fields, models
from odoo.tools import date_utils


class KarmaTracking(models.Model):
    _name = 'gamification.karma.tracking'
    _description = 'Track Karma Changes'
    _rec_name = 'user_id'
    _order = 'tracking_date desc, id desc'

    def _get_origin_selection_values(self):
        return [('res.users', _('User'))]

    user_id = fields.Many2one('res.users', 'User', index=True, required=True, ondelete='cascade')
    old_value = fields.Integer('Old Karma Value', readonly=True)
    new_value = fields.Integer('New Karma Value', required=True)
    gain = fields.Integer('Gain', compute='_compute_gain', readonly=False)
    consolidated = fields.Boolean('Consolidated')

    tracking_date = fields.Datetime(default=fields.Datetime.now, readonly=True, index=True)
    reason = fields.Text(default=lambda self: _('Add Manually'), string='Description')
    origin_ref = fields.Reference(
        string='Source',
        selection=lambda self: self._get_origin_selection_values(),
        default=lambda self: f'res.users,{self.env.user.id}',
    )
    origin_ref_model_name = fields.Selection(
        string='Source Type', selection=lambda self: self._get_origin_selection_values(),
        compute='_compute_origin_ref_model_name', store=True)

    @api.depends('old_value', 'new_value')
    def _compute_gain(self):
        for karma in self:
            karma.gain = karma.new_value - (karma.old_value or 0)

    @api.depends('origin_ref')
    def _compute_origin_ref_model_name(self):
        for karma in self:
            if not karma.origin_ref:
                karma.origin_ref_model_name = False
                continue

            karma.origin_ref_model_name = karma.origin_ref._name

    @api.model_create_multi
    def create(self, values_list):
        # fill missing old value with current user karma
        users = self.env['res.users'].browse([
            values['user_id']
            for values in values_list
            if 'old_value' not in values and values.get('user_id')
        ])
        karma_per_users = {user.id: user.karma for user in users}

        for values in values_list:
            if 'old_value' not in values and values.get('user_id'):
                values['old_value'] = karma_per_users[values['user_id']]

            if 'gain' in values and 'old_value' in values:
                values['new_value'] = values['old_value'] + values['gain']
                del values['gain']

        return super().create(values_list)

    @api.model
    def _consolidate_cron(self):
        """Consolidate the trackings 2 months ago. Used by a cron to cleanup tracking records."""
        from_date = date_utils.start_of(fields.Datetime.today(), 'month') - relativedelta(months=2)
        return self._process_consolidate(from_date)

    def _process_consolidate(self, from_date, end_date=None):
        """Consolidate the karma trackings.

        The consolidation keeps, for each user, the oldest "old_value" and the most recent
        "new_value", creates a new karma tracking with those values and removes all karma
        trackings between those dates. The origin / reason is changed on the consolidated
        records, so this information is lost in the process.
        """
        self.env['gamification.karma.tracking'].flush_model()

        if not end_date:
            end_date = date_utils.end_of(date_utils.end_of(from_date, 'month'), 'day')

        select_query = """
        WITH old_tracking AS (
            SELECT DISTINCT ON (user_id) user_id, old_value, tracking_date
              FROM gamification_karma_tracking
             WHERE tracking_date BETWEEN %(from_date)s
               AND %(end_date)s
               AND consolidated IS NOT TRUE
          ORDER BY user_id, tracking_date ASC, id ASC
        )
            INSERT INTO gamification_karma_tracking (
                            user_id,
                            old_value,
                            new_value,
                            tracking_date,
                            origin_ref,
                            consolidated,
                            reason)
            SELECT DISTINCT ON (nt.user_id)
                            nt.user_id,
                            ot.old_value AS old_value,
                            nt.new_value AS new_value,
                            ot.tracking_date AS from_tracking_date,
                            %(origin_ref)s AS origin_ref,
                            TRUE,
                            %(reason)s
              FROM gamification_karma_tracking AS nt
              JOIN old_tracking AS ot
                   ON ot.user_id = nt.user_id
             WHERE nt.tracking_date BETWEEN %(from_date)s
               AND %(end_date)s
               AND nt.consolidated IS NOT TRUE
          ORDER BY nt.user_id, nt.tracking_date DESC, id DESC
        """

        self.env.cr.execute(select_query, {
            'from_date': from_date,
            'end_date': end_date,
            'origin_ref': f'res.users,{self.env.user.id}',
            'reason': _('Consolidation from %s to %s', from_date.date(), end_date.date()),
        })

        trackings = self.search([
            ('tracking_date', '>=', from_date),
            ('tracking_date', '<=', end_date),
            ('consolidated', '!=', True)]
        )
        # HACK: the unlink() AND the flush_all() must have that key in their context!
        trackings = trackings.with_context(skip_karma_computation=True)
        trackings.unlink()
        trackings.env.flush_all()
        return True
