# Copyright 2019-20 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).

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


class MakeProcurementBuffer(models.TransientModel):
    _name = "make.procurement.buffer"
    _description = "Make Procurements from Stock Buffers"

    partner_id = fields.Many2one(
        comodel_name="res.partner",
        string="Vendor",
        help="If set, will be used as preferred vendor for purchase routes.",
    )
    item_ids = fields.One2many(
        comodel_name="make.procurement.buffer.item",
        inverse_name="wiz_id",
        string="Items",
    )

    @api.model
    def _prepare_item(self, buffer, qty_override=None):
        qty = (
            qty_override if qty_override is not None else buffer.procure_recommended_qty
        )
        return {
            "recommended_qty": buffer.procure_recommended_qty,
            "qty": qty,
            "uom_id": buffer.procure_uom_id.id or buffer.product_uom.id,
            "date_planned": buffer._get_date_planned(),
            "buffer_id": buffer.id,
            "product_id": buffer.product_id.id,
            "warehouse_id": buffer.warehouse_id.id,
            "location_id": buffer.location_id.id,
        }

    @api.model
    def default_get(self, fields):
        res = super().default_get(fields)
        buffer_obj = self.env["stock.buffer"]
        buffer_ids = self.env.context["active_ids"] or []
        active_model = self.env.context["active_model"]

        if not buffer_ids:
            return res
        assert active_model == "stock.buffer", "Bad context propagation"

        items = []
        for line in buffer_obj.browse(buffer_ids):
            max_order = line.procure_max_qty
            qty_to_order = line._procure_qty_to_order()

            if max_order and max_order < qty_to_order:
                # split the procurement in batches:
                while qty_to_order > 0.0:
                    if qty_to_order > max_order:
                        qty = max_order
                    else:
                        rounding = (
                            line.procure_uom_id.rounding or line.product_uom.rounding
                        )
                        profile = line.buffer_profile_id
                        limit_to_free_qty = (
                            line.item_type == "distributed"
                            and profile.replenish_distributed_limit_to_free_qty
                        )
                        if limit_to_free_qty:
                            if (
                                float_compare(
                                    qty_to_order,
                                    line.procure_min_qty,
                                    precision_rounding=rounding,
                                )
                                < 0
                            ):
                                # not enough in stock to have a batch
                                # respecting the min qty!
                                break
                            # not more that what we have in stock!
                            qty = qty_to_order
                        else:
                            # FIXME it will apply a second time the unit conversion
                            qty = line._adjust_procure_qty(qty_to_order)
                    items.append([0, 0, self._prepare_item(line, qty)])
                    qty_to_order -= qty
            else:
                items.append([0, 0, self._prepare_item(line, qty_to_order)])
        res["item_ids"] = items
        return res

    def _get_procurement_location(self, item):
        return item.buffer_id.location_id

    def _get_procurement_name(self, item):
        return item.buffer_id.name

    def _get_procurement_origin(self, item):
        return item.buffer_id.name

    def make_procurement(self):
        self.ensure_one()
        errors = []
        pg_obj = self.env["procurement.group"]
        procurements = []
        for item in self.item_ids:
            # As procurement is processed with SUPERUSER_ID (see _run_buy and
            # _run_manufacture), ensure the current user has write access on
            # the buffer to make a procurement. Otherwise, any user can request
            # a procurement on any buffer
            item.buffer_id.check_access_rights("write")
            item.buffer_id.check_access_rule("write")
            if item.qty <= 0.0:
                raise ValidationError(_("Quantity must be positive."))
            if not item.buffer_id:
                raise ValidationError(_("No stock buffer found."))
            values = item._prepare_values_make_procurement()
            proc_location = self._get_procurement_location(item)
            proc_name = self._get_procurement_name(item)
            proc_origin = self._get_procurement_origin(item)
            procurements.append(
                pg_obj.Procurement(
                    item.buffer_id.product_id,
                    item.qty,
                    item.uom_id,
                    proc_location,
                    proc_name,
                    proc_origin,
                    item.buffer_id.company_id,
                    values,
                )
            )
        # Run procurements
        try:
            pg_obj.run(procurements)
        except UserError as error:
            errors.append(error.name)
        if errors:
            raise UserError("\n".join(errors))
        # Update buffer computed fields:
        buffers = self.mapped("item_ids.buffer_id")
        buffers.invalidate_recordset()
        self.env.add_to_compute(buffers._fields["procure_recommended_qty"], buffers)
        buffers.flush_recordset()
        return {"type": "ir.actions.act_window_close"}


class MakeProcurementBufferItem(models.TransientModel):
    _name = "make.procurement.buffer.item"
    _description = "Make Procurements from Stock Buffer Item"

    wiz_id = fields.Many2one(
        comodel_name="make.procurement.buffer",
        string="Wizard",
        required=True,
        ondelete="cascade",
        readonly=True,
    )
    recommended_qty = fields.Float(readonly=True)
    qty = fields.Float()
    uom_id = fields.Many2one(
        string="Unit of Measure",
        comodel_name="uom.uom",
    )
    date_planned = fields.Datetime(string="Planned Date", required=False)
    buffer_id = fields.Many2one(
        string="Stock Buffer",
        comodel_name="stock.buffer",
        readonly=False,
    )
    product_id = fields.Many2one(
        string="Product",
        comodel_name="product.product",
        readonly=True,
    )
    warehouse_id = fields.Many2one(
        string="Warehouse",
        comodel_name="stock.warehouse",
        readonly=True,
    )
    location_id = fields.Many2one(
        string="Location",
        comodel_name="stock.location",
        readonly=True,
    )

    @api.onchange("uom_id")
    def onchange_uom_id(self):
        for rec in self:
            rec.qty = rec.buffer_id.product_uom._compute_quantity(
                rec.buffer_id.procure_recommended_qty, rec.uom_id
            )

    def _prepare_values_make_procurement(self):
        values = self.buffer_id._prepare_procurement_values(self.qty)
        values.update(
            {"date_planned": self.date_planned, "supplier_id": self.wiz_id.partner_id}
        )
        return values
