# Copyright 2009-2018 Noviat
# Copyright 2021 Tecnativa - João Marques
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

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


class AccountAssetLine(models.Model):
    _name = "account.asset.line"
    _description = "Asset depreciation table line"
    _order = "type, line_date"
    _check_company_auto = True

    name = fields.Char(string="Depreciation Name", size=64, readonly=True)
    asset_id = fields.Many2one(
        comodel_name="account.asset",
        string="Asset",
        required=True,
        ondelete="cascade",
        check_company=True,
        index=True,
    )
    previous_id = fields.Many2one(
        comodel_name="account.asset.line",
        string="Previous Depreciation Line",
        readonly=True,
    )
    parent_state = fields.Selection(
        related="asset_id.state",
        string="State of Asset",
    )
    depreciation_base = fields.Monetary(
        related="asset_id.depreciation_base",
        string="Depreciation Base",
    )
    amount = fields.Monetary(required=True)
    remaining_value = fields.Monetary(
        compute="_compute_values",
        string="Next Period Depreciation",
        store=True,
    )
    depreciated_value = fields.Monetary(
        compute="_compute_values",
        string="Amount Already Depreciated",
        store=True,
    )
    line_date = fields.Date(string="Date", required=True)
    line_days = fields.Integer(string="Days", readonly=True)
    move_id = fields.Many2one(
        comodel_name="account.move",
        string="Depreciation Entry",
        readonly=True,
        check_company=True,
    )
    move_check = fields.Boolean(
        compute="_compute_move_check", string="Posted", store=True
    )
    type = fields.Selection(
        selection=[
            ("create", "Depreciation Base"),
            ("depreciate", "Depreciation"),
            ("remove", "Asset Removal"),
        ],
        readonly=True,
        default="depreciate",
    )
    init_entry = fields.Boolean(
        string="Initial Balance Entry",
        help="Set this flag for entries of previous fiscal years "
        "for which Odoo has not generated accounting entries.",
    )
    company_id = fields.Many2one(related="asset_id.company_id", store=True)
    currency_id = fields.Many2one(
        related="asset_id.company_id.currency_id", store=True, string="Company Currency"
    )

    @api.depends("amount", "previous_id", "type")
    def _compute_values(self):
        self.depreciated_value = 0.0
        self.remaining_value = 0.0
        dlines = self
        if self.env.context.get("no_compute_asset_line_ids"):
            # skip compute for lines in unlink
            exclude_ids = self.env.context["no_compute_asset_line_ids"]
            dlines = self.filtered(lambda l: l.id not in exclude_ids)
        dlines = dlines.filtered(lambda l: l.type == "depreciate")
        dlines = dlines.sorted(key=lambda l: l.line_date)
        # Give value 0 to the lines that are not going to be calculated
        # to avoid cache miss error
        all_excluded_lines = self - dlines
        all_excluded_lines.depreciated_value = 0
        all_excluded_lines.remaining_value = 0
        # Group depreciation lines per asset
        asset_ids = dlines.mapped("asset_id")
        grouped_dlines = []
        for asset in asset_ids:
            grouped_dlines.append(dlines.filtered(lambda l: l.asset_id.id == asset.id))
        for dlines in grouped_dlines:
            for i, dl in enumerate(dlines):
                if i == 0:
                    depreciation_base = dl.depreciation_base
                    tmp = depreciation_base - dl.previous_id.remaining_value
                    depreciated_value = dl.previous_id and tmp or 0.0
                    remaining_value = depreciation_base - depreciated_value - dl.amount
                else:
                    depreciated_value += dl.previous_id.amount
                    remaining_value -= dl.amount
                dl.depreciated_value = depreciated_value
                dl.remaining_value = remaining_value

    @api.depends("move_id")
    def _compute_move_check(self):
        for line in self:
            line.move_check = bool(line.move_id)

    @api.onchange("amount")
    def _onchange_amount(self):
        if self.type == "depreciate":
            self.remaining_value = (
                self.depreciation_base - self.depreciated_value - self.amount
            )

    def write(self, vals):
        for dl in self:
            line_date = vals.get("line_date") or dl.line_date
            asset_lines = dl.asset_id.depreciation_line_ids
            if list(vals.keys()) == ["move_id"] and not vals["move_id"]:
                # allow to remove an accounting entry via the
                # 'Delete Move' button on the depreciation lines.
                if not self.env.context.get("unlink_from_asset"):
                    raise UserError(
                        _(
                            "You are not allowed to remove an accounting entry "
                            "linked to an asset."
                            "\nYou should remove such entries from the asset."
                        )
                    )
            elif list(vals.keys()) == ["asset_id"]:
                continue
            elif (
                dl.move_id
                and not self.env.context.get("allow_asset_line_update")
                and dl.type != "create"
            ):
                raise UserError(
                    _(
                        "You cannot change a depreciation line "
                        "with an associated accounting entry."
                    )
                )
            elif vals.get("init_entry"):
                check = asset_lines.filtered(
                    lambda l: l.move_check
                    and l.type == "depreciate"
                    and l.line_date <= line_date
                )
                if check:
                    raise UserError(
                        _(
                            "You cannot set the 'Initial Balance Entry' flag "
                            "on a depreciation line "
                            "with prior posted entries."
                        )
                    )
            elif vals.get("line_date"):
                if dl.type == "create":
                    check = asset_lines.filtered(
                        lambda l: l.type != "create"
                        and (l.init_entry or l.move_check)
                        and l.line_date < fields.Date.to_date(vals["line_date"])
                    )
                    if check:
                        raise UserError(
                            _(
                                "You cannot set the Asset Start Date "
                                "after already posted entries."
                            )
                        )
                else:
                    check = asset_lines.filtered(
                        lambda al: al != dl
                        and (al.init_entry or al.move_check)
                        and al.line_date > fields.Date.to_date(vals["line_date"])
                    )
                    if check:
                        raise UserError(
                            _(
                                "You cannot set the date on a depreciation line "
                                "prior to already posted entries."
                            )
                        )
        return super().write(vals)

    def unlink(self):
        for dl in self:
            if dl.type == "create" and dl.amount:
                raise UserError(
                    _("You cannot remove an asset line " "of type 'Depreciation Base'.")
                )
            elif dl.move_id:
                raise UserError(
                    _(
                        "You cannot delete a depreciation line with "
                        "an associated accounting entry."
                    )
                )
            previous = dl.previous_id
            next_line = dl.asset_id.depreciation_line_ids.filtered(
                lambda l: l.previous_id == dl and l not in self
            )
            if next_line:
                next_line.previous_id = previous
        return super(
            AccountAssetLine, self.with_context(no_compute_asset_line_ids=self.ids)
        ).unlink()

    def _setup_move_data(self, depreciation_date):
        asset = self.asset_id
        move_data = {
            "date": depreciation_date,
            "ref": "{} - {}".format(asset.name, self.name),
            "journal_id": asset.profile_id.journal_id.id,
        }
        return move_data

    def _setup_move_line_data(self, depreciation_date, account, ml_type, move):
        """Prepare data to be propagated to account.move.line"""
        asset = self.asset_id
        currency = asset.company_id.currency_id
        amount = self.amount
        amount_comp = currency.compare_amounts(amount, 0)
        analytic_distribution = False
        if ml_type == "depreciation":
            debit = amount_comp < 0 and -amount or 0.0
            credit = amount_comp > 0 and amount or 0.0
        elif ml_type == "expense":
            debit = amount_comp > 0 and amount or 0.0
            credit = amount_comp < 0 and -amount or 0.0
            analytic_distribution = asset.analytic_distribution
        move_line_data = {
            "name": asset.name,
            "ref": self.name,
            "move_id": move.id,
            "account_id": account.id,
            "credit": credit,
            "debit": debit,
            "journal_id": asset.profile_id.journal_id.id,
            "partner_id": asset.partner_id.id,
            "analytic_distribution": analytic_distribution,
            "date": depreciation_date,
            "asset_id": asset.id,
        }
        return move_line_data

    def create_move(self):
        created_move_ids = []
        asset_ids = set()
        ctx = dict(self.env.context, allow_asset=True, check_move_validity=False)
        for line in self:
            asset = line.asset_id
            depreciation_date = line.line_date
            am_vals = line._setup_move_data(depreciation_date)
            move = self.env["account.move"].with_context(**ctx).create(am_vals)
            depr_acc = asset.profile_id.account_depreciation_id
            exp_acc = asset.profile_id.account_expense_depreciation_id
            aml_d_vals = line._setup_move_line_data(
                depreciation_date, depr_acc, "depreciation", move
            )
            self.env["account.move.line"].with_context(**ctx).create(aml_d_vals)
            aml_e_vals = line._setup_move_line_data(
                depreciation_date, exp_acc, "expense", move
            )
            self.env["account.move.line"].with_context(**ctx).create(aml_e_vals)
            move.action_post()
            line.with_context(allow_asset_line_update=True).write({"move_id": move.id})
            created_move_ids.append(move.id)
            asset_ids.add(asset.id)
        # we re-evaluate the assets to determine if we can close them
        for asset in self.env["account.asset"].browse(list(asset_ids)):
            if asset.currency_id.is_zero(asset.value_residual):
                asset.state = "close"
        return created_move_ids

    def open_move(self):
        self.ensure_one()
        return {
            "name": _("Journal Entry"),
            "view_mode": "form",
            "res_id": self.move_id.id,
            "res_model": "account.move",
            "view_id": False,
            "type": "ir.actions.act_window",
            "context": self.env.context,
        }

    def update_asset_line_after_unlink_move(self):
        self.write({"move_id": False})
        if self.parent_state == "close":
            self.asset_id.write({"state": "open"})
        elif self.parent_state == "removed" and self.type == "remove":
            self.asset_id.write({"state": "close", "date_remove": False})
            self.unlink()

    def unlink_move(self):
        for line in self:
            if line.asset_id.profile_id.allow_reversal:
                context = dict(self._context or {})
                context.update(
                    {
                        "active_model": self._name,
                        "active_ids": line.ids,
                        "active_id": line.id,
                    }
                )
                return {
                    "name": _("Reverse Move"),
                    "view_mode": "form",
                    "res_model": "wiz.asset.move.reverse",
                    "target": "new",
                    "type": "ir.actions.act_window",
                    "context": context,
                }
            else:
                move = line.move_id
                move.button_draft()
                move.with_context(force_delete=True, unlink_from_asset=True).unlink()
                line.with_context(
                    unlink_from_asset=True
                ).update_asset_line_after_unlink_move()
        return True