# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, time
from dateutil.relativedelta import relativedelta

from odoo import fields, models, _, api, Command
from odoo.exceptions import UserError
from odoo.tools import format_list


class MrpWipAccountingLine(models.TransientModel):
    _name = 'mrp.account.wip.accounting.line'
    _description = 'Account move line to be created when posting WIP account move'

    account_id = fields.Many2one('account.account', "Account")
    label = fields.Char("Label")
    debit = fields.Monetary("Debit", compute='_compute_debit', store=True, readonly=False)
    credit = fields.Monetary("Credit", compute='_compute_credit', store=True, readonly=False)
    currency_id = fields.Many2one('res.currency', "Currency", default=lambda self: self.env.company.currency_id)
    wip_accounting_id = fields.Many2one('mrp.account.wip.accounting', "WIP accounting wizard")

    _sql_constraints = [
        ('check_debit_credit', 'CHECK ( debit = 0 OR credit = 0 )',
         'A single line cannot be both credit and debit.')
    ]

    @api.depends('credit')
    def _compute_debit(self):
        for record in self:
            if not record.currency_id.is_zero(record.credit):
                record.debit = 0

    @api.depends('debit')
    def _compute_credit(self):
        for record in self:
            if not record.currency_id.is_zero(record.debit):
                record.credit = 0


class MrpWipAccounting(models.TransientModel):
    _name = 'mrp.account.wip.accounting'
    _description = 'Wizard to post Manufacturing WIP account move'

    @api.model
    def default_get(self, fields_list):
        res = super().default_get(fields_list)
        productions = self.env['mrp.production'].browse(self.env.context.get('active_ids'))
        # ignore selected MOs that aren't a WIP
        productions = productions.filtered(lambda mo: mo.state in ['progress', 'to_close', 'confirmed'])
        if 'journal_id' in fields_list:
            default = self.env['product.category']._fields['property_stock_journal'].get_company_dependent_fallback(self.env['product.category'])
            if default:
                res['journal_id'] = default.id
        if 'reference' in fields_list:
            res['reference'] = _("Manufacturing WIP - %(orders_list)s", orders_list=productions and format_list(self.env, productions.mapped('name')) or _("Manual Entry"))
        if 'mo_ids' in fields_list:
            res['mo_ids'] = [Command.set(productions.ids)]
        return res

    date = fields.Date("Date", default=fields.Datetime.now)
    reversal_date = fields.Date(
        "Reversal Date", compute="_compute_reversal_date", required=True,
        store=True, readonly=False)
    journal_id = fields.Many2one('account.journal', "Journal", required=True)
    reference = fields.Char("Reference")
    line_ids = fields.One2many(
        'mrp.account.wip.accounting.line', 'wip_accounting_id', "WIP accounting lines",
        compute="_compute_line_ids", store=True, readonly=False)
    mo_ids = fields.Many2many('mrp.production')

    def _get_overhead_account(self):
        overhead_account = self.env.company.account_production_wip_overhead_account_id
        if overhead_account:
            return overhead_account.id
        ProductCategory = self.env['product.category']
        cop_acc = ProductCategory._fields['property_stock_account_production_cost_id'].get_company_dependent_fallback(ProductCategory)
        if cop_acc:
            return cop_acc.id
        return ProductCategory._fields['property_stock_account_input_categ_id'].get_company_dependent_fallback(ProductCategory).id

    def _get_line_vals(self, productions=False, date=False):
        if not productions:
            productions = self.env['mrp.production']
        if not date:
            date = datetime.now().replace(hour=23, minute=59, second=59)
        compo_value = sum(
            ml.quantity_product_uom * (ml.product_id.lot_valuated and ml.lot_id and ml.lot_id.standard_price or ml.product_id.standard_price)
            for ml in productions.move_raw_ids.move_line_ids.filtered(lambda ml: ml.picked and ml.quantity and ml.date <= date)
        )
        overhead_value = productions.workorder_ids._cal_cost(date)
        sval_acc = self.env['product.category']._fields['property_stock_valuation_account_id'].get_company_dependent_fallback(self.env['product.category']).id
        return [
            Command.create({
                'label': _("WIP - Component Value"),
                'credit': compo_value,
                'account_id': sval_acc,
            }),
            Command.create({
                'label': _("WIP - Overhead"),
                'credit': overhead_value,
                'account_id': self._get_overhead_account(),
            }),
            Command.create({
                'label': _("Manufacturing WIP - %(orders_list)s", orders_list=productions and format_list(self.env, productions.mapped('name')) or _("Manual Entry")),
                'debit': compo_value + overhead_value,
                'account_id': self.env.company.account_production_wip_account_id.id,
            })
        ]

    @api.depends('date')
    def _compute_reversal_date(self):
        for wizard in self:
            if not wizard.reversal_date or wizard.reversal_date <= wizard.date:
                wizard.reversal_date = wizard.date + relativedelta(days=1)
            else:
                wizard.reversal_date = wizard.reversal_date

    @api.depends('date')
    def _compute_line_ids(self):
        for wizard in self:
            # don't update lines when manual (i.e. no applicable MOs) entry
            if not wizard.line_ids or wizard.mo_ids:
                wizard.line_ids = [Command.clear()] + wizard._get_line_vals(wizard.mo_ids, datetime.combine(wizard.date, time.max))

    def confirm(self):
        self.ensure_one()
        if self.env.company.currency_id.compare_amounts(sum(self.line_ids.mapped('credit')), sum(self.line_ids.mapped('debit'))) != 0:
            raise UserError(_("Please make sure the total credit amount equals the total debit amount."))
        if self.reversal_date <= self.date:
            raise UserError(_("Reversal date must be after the posting date."))
        move = self.env['account.move'].sudo().create({
            'journal_id': self.journal_id.id,
            'wip_production_ids': self.mo_ids.ids,
            'date': self.date,
            'ref': self.reference,
            'move_type': 'entry',
            'line_ids': [
                Command.create({
                    'name': line.label,
                    'account_id': line.account_id.id,
                    'debit': line.debit,
                    'credit': line.credit,
                }) for line in self.line_ids
            ]
        })
        move._post()
        move._reverse_moves(default_values_list=[{
            'ref': _("Reversal of: %s", self.reference),
            'wip_production_ids': self.mo_ids.ids,
            'date': self.reversal_date,
        }])._post()
