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

from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError

import ast


class CouponProgram(models.Model):
    _name = 'coupon.program'
    _description = "Coupon Program"
    _inherits = {'coupon.rule': 'rule_id', 'coupon.reward': 'reward_id'}
    # We should apply 'discount' promotion first to avoid offering free product when we should not.
    # Eg: If the discount lower the SO total below the required threshold
    # Note: This is only revelant when programs have the same sequence (which they have by default)
    _order = "sequence, reward_type"

    name = fields.Char(required=True, translate=True)
    active = fields.Boolean('Active', default=True, help="A program is available for the customers when active")
    rule_id = fields.Many2one('coupon.rule', string="Coupon Rule", ondelete='restrict', required=True)
    reward_id = fields.Many2one('coupon.reward', string="Reward", ondelete='restrict', required=True, copy=False)
    sequence = fields.Integer(copy=False,
        help="Coupon program will be applied based on given sequence if multiple programs are " +
        "defined on same condition(For minimum amount)")
    maximum_use_number = fields.Integer(help="Maximum number of sales orders in which reward can be provided")
    program_type = fields.Selection([
        ('promotion_program', 'Promotional Program'),
        ('coupon_program', 'Coupon Program'),
        ],
        help="""A promotional program can be either a limited promotional offer without code (applied automatically)
                or with a code (displayed on a magazine for example) that may generate a discount on the current
                order or create a coupon for a next order.

                A coupon program generates coupons with a code that can be used to generate a discount on the current
                order or create a coupon for a next order.""")
    promo_code_usage = fields.Selection([
        ('no_code_needed', 'Automatically Applied'),
        ('code_needed', 'Use a code')],
        help="Automatically Applied - No code is required, if the program rules are met, the reward is applied (Except the global discount or the free shipping rewards which are not cumulative)\n" +
             "Use a code - If the program rules are met, a valid code is mandatory for the reward to be applied\n")
    promo_code = fields.Char('Promotion Code', copy=False,
        help="A promotion code is a code that is associated with a marketing discount. For example, a retailer might tell frequent customers to enter the promotion code 'THX001' to receive a 10%% discount on their whole order.")
    promo_applicability = fields.Selection([
        ('on_current_order', 'Apply On Current Order'),
        ('on_next_order', 'Send a Coupon')],
        default='on_current_order', string="Applicability")
    coupon_ids = fields.One2many('coupon.coupon', 'program_id', string="Generated Coupons", copy=False)
    coupon_count = fields.Integer(compute='_compute_coupon_count')
    company_id = fields.Many2one('res.company', string="Company", required=True, default=lambda self: self.env.company)
    currency_id = fields.Many2one(string="Currency", related='company_id.currency_id', readonly=True)
    validity_duration = fields.Integer(default=30,
        help="Validity duration for a coupon after its generation")
    total_order_count = fields.Integer("Total Order Count", compute="_compute_total_order_count")

    @api.constrains('promo_code')
    def _check_promo_code_constraint(self):
        """ Program code must be unique """
        for program in self.filtered(lambda p: p.promo_code):
            domain = [('id', '!=', program.id), ('promo_code', '=', program.promo_code)]
            if self.search(domain):
                raise ValidationError(_('The program code must be unique!'))

    @api.depends('coupon_ids')
    def _compute_coupon_count(self):
        coupon_data = self.env['coupon.coupon'].read_group([('program_id', 'in', self.ids)], ['program_id'], ['program_id'])
        mapped_data = dict([(m['program_id'][0], m['program_id_count']) for m in coupon_data])
        for program in self:
            program.coupon_count = mapped_data.get(program.id, 0)

    @api.onchange('promo_code_usage')
    def _onchange_promo_code_usage(self):
        if self.promo_code_usage == 'no_code_needed':
            self.promo_code = False

    @api.onchange('reward_product_id')
    def _onchange_reward_product_id(self):
        if self.reward_product_id:
            self.reward_product_uom_id = self.reward_product_id.uom_id

    @api.onchange('discount_type')
    def _onchange_discount_type(self):
        if self.discount_type == 'fixed_amount':
            self.discount_apply_on = 'on_order'

    @api.model_create_multi
    def create(self, vals_list):
        programs = super().create(vals_list)
        for program in programs:
            if program.discount_line_product_id:
                continue
            values = program._get_discount_product_values()
            discount_line_product_id = self.env['product.product'].create(values)
            program.write({'discount_line_product_id': discount_line_product_id.id})
        return programs

    def write(self, vals):
        res = super(CouponProgram, self).write(vals)
        reward_fields = [
            'reward_type', 'reward_product_id', 'discount_type', 'discount_percentage',
            'discount_apply_on', 'discount_specific_product_ids', 'discount_fixed_amount'
        ]
        if any(field in reward_fields for field in vals):
            self.mapped('discount_line_product_id').write({'name': self[0].reward_id.display_name})
        return res

    @api.ondelete(at_uninstall=False)
    def _unlink_except_active(self):
        if self.filtered('active'):
            raise UserError(_('You can not delete a program in active state'))

    def unlink(self):
        # get reference to rule and reward
        rule = self.rule_id
        reward = self.reward_id
        # unlink the program
        super(CouponProgram, self).unlink()
        # then unlink the rule and reward
        rule.unlink()
        reward.unlink()
        return True

    def toggle_active(self):
        super(CouponProgram, self).toggle_active()
        for program in self:
            program.discount_line_product_id.active = program.active
        coupons = self.filtered(lambda p: not p.active and p.promo_code_usage == 'code_needed').mapped('coupon_ids')
        coupons.filtered(lambda x: x.state != 'used').write({'state': 'expired'})

    def _compute_program_amount(self, field, currency_to):
        self.ensure_one()
        return self.currency_id._convert(self[field], currency_to, self.company_id, fields.Date.today())

    def _is_valid_partner(self, partner):
        if self.rule_partners_domain and self.rule_partners_domain != '[]':
            domain = ast.literal_eval(self.rule_partners_domain) + [('id', '=', partner.id)]
            return bool(self.env['res.partner'].search_count(domain))
        else:
            return True

    def _get_valid_products(self, products):
        if self.rule_products_domain:
            domain = ast.literal_eval(self.rule_products_domain)
            return products.filtered_domain(domain)
        return products

    def _generate_coupons(self, partner_id):
        '''Generate coupons that can be used in the next order for the given partner_id.'''
        generated_coupons = self.env['coupon.coupon']
        for program in self:
            generated_coupons |= self.env['coupon.coupon'].create({
                'program_id': program.id,
                'partner_id': partner_id,
            })
        return generated_coupons

    def _compute_total_order_count(self):
        for program in self:
            program.total_order_count = 0

    def _get_discount_product_values(self):
        return {
            'name': self.reward_id.display_name,
            'type': 'service',
            'taxes_id': False,
            'supplier_taxes_id': False,
            'sale_ok': False,
            'purchase_ok': False,
            'lst_price': 0, #Do not set a high value to avoid issue with coupon code
        }
