# Copyright 2015-2019 See manifest
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html

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


class AccountMoveTemplate(models.Model):
    _name = "account.move.template"
    _description = "Journal Entry Template"
    _check_company_auto = True

    name = fields.Char(required=True)
    company_id = fields.Many2one(
        "res.company",
        string="Company",
        required=True,
        ondelete="cascade",
        default=lambda self: self.env.company,
    )
    journal_id = fields.Many2one(
        "account.journal",
        string="Journal",
        required=True,
        check_company=True,
        domain="[('company_id', '=', company_id)]",
    )
    ref = fields.Char(string="Reference", copy=False)
    line_ids = fields.One2many(
        "account.move.template.line", inverse_name="template_id", string="Lines"
    )
    active = fields.Boolean(default=True)

    _sql_constraints = [
        (
            "name_company_unique",
            "unique(name, company_id)",
            "This name is already used by another template!",
        )
    ]

    def copy(self, default=None):
        self.ensure_one()
        default = dict(default or {}, name=_("%s (copy)") % self.name)
        return super().copy(default)

    def eval_computed_line(self, line, sequence2amount):
        safe_eval_dict = {}
        for seq, amount in sequence2amount.items():
            safe_eval_dict["L%d" % seq] = amount
        try:
            val = safe_eval(line.python_code, safe_eval_dict)
            sequence2amount[line.sequence] = val
        except ValueError as err:
            raise UserError(
                _(
                    "Impossible to compute the formula of line with sequence %(sequence)s "
                    "(formula: %(code)s). Check that the lines used in the formula "
                    "really exists and have a lower sequence than the current "
                    "line.",
                    sequence=line.sequence,
                    code=line.python_code,
                )
            ) from err
        except SyntaxError as err:
            raise UserError(
                _(
                    "Impossible to compute the formula of line with sequence %(sequence)s "
                    "(formula: %(code)s): the syntax of the formula is wrong.",
                    sequence=line.sequence,
                    code=line.python_code,
                )
            ) from err

    def compute_lines(self, sequence2amount):
        company_cur = self.company_id.currency_id
        input_sequence2amount = sequence2amount.copy()
        for line in self.line_ids.filtered(lambda x: x.type == "input"):
            if line.sequence not in sequence2amount:
                raise UserError(
                    _(
                        "You deleted a line in the wizard. This is not allowed: "
                        "you should either update the template or modify the "
                        "journal entry that will be generated by this wizard."
                    )
                )
            input_sequence2amount.pop(line.sequence)
        if input_sequence2amount:
            raise UserError(
                _(
                    "You added a line in the wizard. This is not allowed: "
                    "you should either update the template or modify "
                    "the journal entry that will be generated by this wizard."
                )
            )
        for line in self.line_ids.filtered(lambda x: x.type == "computed"):
            self.eval_computed_line(line, sequence2amount)
            sequence2amount[line.sequence] = company_cur.round(
                sequence2amount[line.sequence]
            )
        return sequence2amount

    def generate_journal_entry(self):
        """Called by the button on the form view"""
        self.ensure_one()
        wiz = self.env["account.move.template.run"].create({"template_id": self.id})
        action = wiz.load_lines()
        return action


class AccountMoveTemplateLine(models.Model):
    _name = "account.move.template.line"
    _description = "Journal Item Template"
    _order = "sequence, id"
    _inherit = "analytic.mixin"
    _check_company_auto = True

    template_id = fields.Many2one(
        "account.move.template", string="Move Template", ondelete="cascade"
    )
    name = fields.Char(string="Label")
    sequence = fields.Integer(required=True)
    account_id = fields.Many2one(
        "account.account",
        string="Account",
        required=True,
        domain="[('company_id', '=', company_id), ('deprecated', '=', False)]",
        check_company=True,
    )
    partner_id = fields.Many2one(
        "res.partner",
        string="Partner",
        domain=["|", ("parent_id", "=", False), ("is_company", "=", True)],
    )
    tax_ids = fields.Many2many("account.tax", string="Taxes", check_company=True)
    tax_line_id = fields.Many2one(
        "account.tax", string="Originator Tax", ondelete="restrict", check_company=True
    )
    company_id = fields.Many2one(related="template_id.company_id", store=True)
    company_currency_id = fields.Many2one(
        related="template_id.company_id.currency_id",
        string="Company Currency",
        store=True,
    )
    note = fields.Char()
    type = fields.Selection(
        [
            ("input", "User input"),
            ("computed", "Computed"),
        ],
        required=True,
        default="input",
    )
    python_code = fields.Text(string="Formula")
    move_line_type = fields.Selection(
        [("cr", "Credit"), ("dr", "Debit")], required=True, string="Direction"
    )
    payment_term_id = fields.Many2one(
        "account.payment.term",
        string="Payment Terms",
        help="Used to compute the due date of the journal item.",
    )
    is_refund = fields.Boolean(
        default=False,
        string="Is a refund?",
    )
    tax_repartition_line_id = fields.Many2one(
        "account.tax.repartition.line",
        string="Tax Repartition Line",
        compute="_compute_tax_repartition_line_id",
        store=True,
    )
    opt_account_id = fields.Many2one(
        "account.account",
        string="Account Opt.",
        domain="[('company_id', '=', company_id), ('deprecated', '=', False)]",
        check_company=True,
        help="When amount is negative, use this account instead",
    )

    @api.depends("is_refund", "account_id", "tax_line_id")
    def _compute_tax_repartition_line_id(self):
        for record in self.filtered(lambda x: x.account_id and x.tax_line_id):
            tax_repartition = "refund_tax_id" if record.is_refund else "invoice_tax_id"
            record.tax_repartition_line_id = self.env[
                "account.tax.repartition.line"
            ].search(
                [
                    ("account_id", "=", record.account_id.id),
                    (tax_repartition, "=", record.tax_line_id.id),
                ],
                limit=1,
            )

    @api.depends("account_id", "partner_id")
    def _compute_analytic_distribution(self):
        for line in self:
            distribution = self.env[
                "account.analytic.distribution.model"
            ]._get_distribution(
                {
                    "partner_id": line.partner_id.id,
                    "partner_category_id": line.partner_id.category_id.ids,
                    "product_id": False,
                    "product_categ_id": False,
                    "account_prefix": line.account_id.code,
                    "company_id": line.template_id.company_id.id,
                }
            )
            line.analytic_distribution = distribution or line.analytic_distribution

    _sql_constraints = [
        (
            "sequence_template_uniq",
            "unique(template_id, sequence)",
            "The sequence of the line must be unique per template!",
        )
    ]

    @api.constrains("type", "python_code")
    def check_python_code(self):
        for line in self:
            if line.type == "computed" and not line.python_code:
                raise ValidationError(
                    _("Python Code must be set for computed line with sequence %d.")
                    % line.sequence
                )