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

from odoo import api, fields, models

class SaleOrderLine(models.Model):
    _inherit = "sale.order.line"

    is_reward_line = fields.Boolean('Is a program reward line', compute='_compute_is_reward_line')
    reward_id = fields.Many2one('loyalty.reward', ondelete='restrict', readonly=True)
    coupon_id = fields.Many2one('loyalty.card', ondelete='restrict', readonly=True)
    reward_identifier_code = fields.Char(help="""
        Technical field used to link multiple reward lines from the same reward together.
    """)
    points_cost = fields.Float(help='How much point this reward cost on the loyalty card.')

    def _compute_name(self):
        # Avoid computing the name for reward lines
        reward = self.filtered('reward_id')
        super(SaleOrderLine, self - reward)._compute_name()

    @api.depends('reward_id')
    def _compute_is_reward_line(self):
        for line in self:
            line.is_reward_line = bool(line.reward_id)

    def _compute_tax_id(self):
        reward_lines = self.filtered('is_reward_line')
        super(SaleOrderLine, self - reward_lines)._compute_tax_id()
        # Discount reward line is split per tax, the discount is set on the line but not on the product
        # as the product is the generic discount line.
        # In case of a free product, retrieving the tax on the line instead of the product won't affect the behavior.
        for line in reward_lines:
            line = line.with_company(line.company_id)
            fpos = line.order_id.fiscal_position_id or line.order_id.fiscal_position_id._get_fiscal_position(line.order_partner_id)
            # If company_id is set, always filter taxes by the company
            taxes = line.tax_id.filtered(lambda r: not line.company_id or r.company_id == line.company_id)
            line.tax_id = fpos.map_tax(taxes)

    def _get_display_price(self):
        # A product created from a promotion does not have a list_price.
        # The price_unit of a reward order line is computed by the promotion, so it can be used directly
        if self.is_reward_line and self.reward_id.reward_type != 'product':
            return self.price_unit
        return super()._get_display_price()

    def _is_not_sellable_line(self):
        return self.is_reward_line or super()._is_not_sellable_line()

    def _reset_loyalty(self, complete=False):
        """
        Reset the line(s) to a state which does not impact reward computation.
        If complete is set to True we also remove the coupon and reward from the line(s).
            This option should be used when the line will be unlinked.

        Returns self
        """
        vals = {
            'points_cost': 0,
            'price_unit': 0,
        }
        if complete:
            vals.update({
                'coupon_id': False,
                'reward_id': False,
            })
        self.write(vals)
        return self

    @api.model_create_multi
    def create(self, vals_list):
        res = super().create(vals_list)
        # Update our coupon points if the order is in a confirmed state
        for line in res:
            if line.coupon_id and line.points_cost and line.order_id.state in ('sale', 'done'):
                line.coupon_id.points -= line.points_cost
        return res

    def write(self, vals):
        cost_in_vals = 'points_cost' in vals
        if cost_in_vals:
            previous_cost = {l: l.points_cost for l in self}
        res = super().write(vals)
        if cost_in_vals:
            # Update our coupon points if the order is in a confirmed state
            for line in self:
                if previous_cost[line] != line.points_cost and line.order_id.state in ('sale', 'done'):
                    line.coupon_id.points += (previous_cost[line] - line.points_cost)
        return res

    def unlink(self):
        # Remove related reward lines
        reward_coupon_set = {(l.reward_id, l.coupon_id, l.reward_identifier_code) for l in self if l.reward_id}
        related_lines = self.env['sale.order.line']
        related_lines |= self.order_id.order_line.filtered(lambda l: (l.reward_id, l.coupon_id, l.reward_identifier_code) in reward_coupon_set)
        # Remove the line's coupon from order if it is the last line using that coupon
        coupons_to_unlink = self.env['loyalty.card']
        for line in self:
            if line.coupon_id:
                # 2 cases:
                #  case 1: coupon has been applied directly
                #  case 2: coupon was created from a program
                if line.coupon_id in line.order_id.applied_coupon_ids:
                    line.order_id.applied_coupon_ids -= line.coupon_id
                elif line.coupon_id.order_id == line.order_id and line.coupon_id.program_id.applies_on == 'current' and\
                    not any(oLine.coupon_id == line.coupon_id and oLine not in related_lines for oLine in line.order_id.order_line):
                    # ondelete='restrict' would prevent deletion of the coupon unlink after unlinking lines
                    coupons_to_unlink |= line.coupon_id
                    line.order_id.code_enabled_rule_ids = line.order_id.code_enabled_rule_ids.filtered(lambda r: r.program_id != line.coupon_id.program_id)
        # Give back the points if the order is confirmed, points are given back if the order is cancelled but in this case we need to do it directly
        for line in related_lines:
            if line.order_id.state in ('sale', 'done'):
                line.coupon_id.points += line.points_cost
        res = super(SaleOrderLine, self | related_lines).unlink()
        coupons_to_unlink.sudo().unlink()
        return res

    def _sellable_lines_domain(self):
        return super()._sellable_lines_domain() + [('reward_id', '=', False)]
