# Copyright 2025 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)

"""
Tests para el filtrado por stock pronosticado neto en Eskaera.

Cubre:
- _compute_draft_sale_demand: cálculo de demanda de presupuestos draft/sent
- _compute_stock_ribbons: ribbons usando qty neta con contexto draft_demand_by_product
- _get_products_for_group_order: exclusión de productos sin stock neto
"""

from datetime import datetime
from datetime import timedelta

from odoo.tests.common import TransactionCase


class TestDraftSaleDemand(TransactionCase):
    """Tests para group.order._compute_draft_sale_demand."""

    def setUp(self):
        super().setUp()
        self.stock_location = self.env.ref("stock.stock_location_stock")
        self.uom_unit = self.env.ref("uom.product_uom_unit")

        self.product_a = self.env["product.product"].create(
            {
                "name": "Product A",
                "type": "consu",
                "is_storable": True,
                "uom_id": self.uom_unit.id,
                "uom_po_id": self.uom_unit.id,
                "is_published": True,
                "sale_ok": True,
            }
        )
        self.product_b = self.env["product.product"].create(
            {
                "name": "Product B",
                "type": "consu",
                "is_storable": True,
                "uom_id": self.uom_unit.id,
                "uom_po_id": self.uom_unit.id,
                "is_published": True,
                "sale_ok": True,
            }
        )
        self.partner = self.env["res.partner"].create({"name": "Test Customer"})

    def _make_sale_order(self, product, qty, state="draft"):
        """Helper: create a sale.order with one line."""
        so = self.env["sale.order"].create(
            {
                "partner_id": self.partner.id,
                "order_line": [
                    (
                        0,
                        0,
                        {
                            "product_id": product.id,
                            "product_uom_qty": qty,
                            "product_uom": product.uom_id.id,
                            "price_unit": 10.0,
                            "name": product.name,
                        },
                    )
                ],
            }
        )
        if state == "sent":
            so.action_quotation_sent()
        elif state == "sale":
            so.action_confirm()
        return so

    def test_no_draft_orders_returns_zeros(self):
        """Sin presupuestos, la demanda es cero."""
        products = self.product_a | self.product_b
        demand = self.env["group.order"]._compute_draft_sale_demand(products)
        self.assertEqual(demand[self.product_a.id], 0.0)
        self.assertEqual(demand[self.product_b.id], 0.0)

    def test_draft_order_counted(self):
        """Un presupuesto en draft se cuenta."""
        self._make_sale_order(self.product_a, 5.0, state="draft")
        demand = self.env["group.order"]._compute_draft_sale_demand(self.product_a)
        self.assertEqual(demand[self.product_a.id], 5.0)

    def test_sent_order_counted(self):
        """Un presupuesto en estado 'sent' también se cuenta."""
        self._make_sale_order(self.product_a, 3.0, state="sent")
        demand = self.env["group.order"]._compute_draft_sale_demand(self.product_a)
        self.assertEqual(demand[self.product_a.id], 3.0)

    def test_confirmed_order_not_counted(self):
        """Un pedido confirmado (state='sale') no impacta la demanda draft."""
        self._make_sale_order(self.product_a, 7.0, state="sale")
        demand = self.env["group.order"]._compute_draft_sale_demand(self.product_a)
        self.assertEqual(demand[self.product_a.id], 0.0)

    def test_multiple_draft_orders_summed(self):
        """Varios presupuestos draft se suman."""
        self._make_sale_order(self.product_a, 4.0, state="draft")
        self._make_sale_order(self.product_a, 6.0, state="draft")
        demand = self.env["group.order"]._compute_draft_sale_demand(self.product_a)
        self.assertEqual(demand[self.product_a.id], 10.0)

    def test_multiple_products_isolated(self):
        """La demanda de cada producto es independiente."""
        self._make_sale_order(self.product_a, 3.0, state="draft")
        self._make_sale_order(self.product_b, 8.0, state="draft")
        products = self.product_a | self.product_b
        demand = self.env["group.order"]._compute_draft_sale_demand(products)
        self.assertEqual(demand[self.product_a.id], 3.0)
        self.assertEqual(demand[self.product_b.id], 8.0)

    def test_empty_recordset_returns_empty_dict(self):
        """Con recordset vacío, devuelve dict vacío."""
        products = self.env["product.product"].browse()
        demand = self.env["group.order"]._compute_draft_sale_demand(products)
        self.assertEqual(demand, {})


class TestStockRibbonsWithDraftDemand(TransactionCase):
    """Tests para _compute_stock_ribbons con contexto draft_demand_by_product."""

    def setUp(self):
        super().setUp()
        self.stock_location = self.env.ref("stock.stock_location_stock")
        self.uom_unit = self.env.ref("uom.product_uom_unit")
        self.partner = self.env["res.partner"].create({"name": "Ribbon Test Customer"})

        self.product = self.env["product.product"].create(
            {
                "name": "Storable Ribbon Product",
                "type": "consu",
                "is_storable": True,
                "uom_id": self.uom_unit.id,
                "uom_po_id": self.uom_unit.id,
                "allow_out_of_stock_order": False,
            }
        )
        # Configurar umbral bajo para facilitar tests de pocas existencias
        self.env["ir.config_parameter"].sudo().set_param(
            "website_sale_aplicoop.low_stock_threshold", "5.0"
        )

    def _set_stock(self, qty):
        if qty:
            self.env["stock.quant"].sudo()._update_available_quantity(
                self.product, self.stock_location, qty
            )

    def _make_draft_demand(self, qty):
        return {self.product.id: qty}

    def test_in_stock_no_demand_no_ribbon(self):
        """Stock suficiente, sin demanda draft → sin ribbon."""
        self._set_stock(10.0)
        p = self.product.with_context(draft_demand_by_product={self.product.id: 0.0})
        self.assertFalse(p.is_out_of_stock)
        self.assertFalse(p.is_low_stock)

    def test_draft_demand_reduces_to_low_stock(self):
        """Stock 10, demanda draft 8 → qty neta 2 → pocas existencias."""
        self._set_stock(10.0)
        p = self.product.with_context(
            draft_demand_by_product=self._make_draft_demand(8.0)
        )
        self.assertFalse(p.is_out_of_stock)
        self.assertTrue(p.is_low_stock)

    def test_draft_demand_reduces_to_out_of_stock(self):
        """Stock 5, demanda draft 6 → qty neta -1 → fuera de stock."""
        self._set_stock(5.0)
        p = self.product.with_context(
            draft_demand_by_product=self._make_draft_demand(6.0)
        )
        self.assertTrue(p.is_out_of_stock)
        self.assertFalse(p.is_low_stock)

    def test_without_context_uses_virtual_available(self):
        """Sin contexto, el ribbon usa virtual_available (comportamiento anterior)."""
        # Sin stock añadido → virtual_available = 0 → is_out_of_stock = True
        self.assertTrue(self.product.is_out_of_stock)

    def test_allow_out_of_stock_order_bypasses_ribbon(self):
        """Con allow_out_of_stock_order=True nunca hay ribbon, aunque qty neta sea negativa."""
        self._set_stock(1.0)
        self.product.write({"allow_out_of_stock_order": True})
        p = self.product.with_context(
            draft_demand_by_product=self._make_draft_demand(10.0)
        )
        self.assertFalse(p.is_out_of_stock)
        self.assertFalse(p.is_low_stock)

    def test_service_product_never_has_ribbon(self):
        """Producto de tipo servicio nunca tiene ribbon de stock."""
        service = self.env["product.product"].create(
            {"name": "Service", "type": "service"}
        )
        p = service.with_context(draft_demand_by_product={service.id: 999.0})
        self.assertFalse(p.is_out_of_stock)
        self.assertFalse(p.is_low_stock)


class TestGroupOrderForecastedFilter(TransactionCase):
    """Tests para el filtro de stock pronosticado neto en _get_products_for_group_order."""

    def setUp(self):
        super().setUp()
        self.stock_location = self.env.ref("stock.stock_location_stock")
        self.uom_unit = self.env.ref("uom.product_uom_unit")
        self.partner = self.env["res.partner"].create({"name": "Filter Test Customer"})

        start_date = datetime.now().date()
        self.group_order = self.env["group.order"].create(
            {
                "name": "Filter Test Order",
                "type": "regular",
                "start_date": start_date,
                "end_date": start_date + timedelta(days=7),
                "period": "weekly",
                "pickup_day": "3",
                "cutoff_day": "0",
            }
        )

    def _storable(self, name, stock=0.0, allow_oos=False):
        """Helper: crear producto almacenable con stock y configuración dada."""
        p = self.env["product.product"].create(
            {
                "name": name,
                "type": "consu",
                "is_storable": True,
                "uom_id": self.uom_unit.id,
                "uom_po_id": self.uom_unit.id,
                "is_published": True,
                "sale_ok": True,
                "allow_out_of_stock_order": allow_oos,
            }
        )
        if stock:
            self.env["stock.quant"].sudo()._update_available_quantity(
                p, self.stock_location, stock
            )
        return p

    def _draft_so(self, product, qty):
        """Helper: crear sale.order draft con una línea."""
        return self.env["sale.order"].create(
            {
                "partner_id": self.partner.id,
                "order_line": [
                    (
                        0,
                        0,
                        {
                            "product_id": product.id,
                            "product_uom_qty": qty,
                            "product_uom": product.uom_id.id,
                            "price_unit": 10.0,
                            "name": product.name,
                        },
                    )
                ],
            }
        )

    def test_product_with_sufficient_net_stock_included(self):
        """Producto con stock neto positivo aparece en el pedido de grupo."""
        p = self._storable("Enough Stock", stock=10.0)
        self._draft_so(p, 3.0)  # neto = 7
        self.group_order.product_ids = [(4, p.id)]
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertIn(p, products)

    def test_product_with_zero_net_stock_excluded(self):
        """Producto con stock neto = 0 es excluido."""
        p = self._storable("Exact Zero", stock=5.0)
        self._draft_so(p, 5.0)  # neto = 0
        self.group_order.product_ids = [(4, p.id)]
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertNotIn(p, products)

    def test_product_with_negative_net_stock_excluded(self):
        """Producto con stock neto negativo (más demanda que stock) es excluido."""
        p = self._storable("Negative Net", stock=3.0)
        self._draft_so(p, 7.0)  # neto = -4
        self.group_order.product_ids = [(4, p.id)]
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertNotIn(p, products)

    def test_product_with_allow_oos_always_included(self):
        """Producto con allow_out_of_stock_order=True aparece aunque no haya stock neto."""
        p = self._storable("Allow OOS", stock=2.0, allow_oos=True)
        self._draft_so(p, 10.0)  # neto = -8, pero allow_oos=True
        self.group_order.product_ids = [(4, p.id)]
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertIn(p, products)

    def test_service_product_always_included(self):
        """Productos de servicio (type!='consu') no se filtran por stock."""
        service = self.env["product.product"].create(
            {
                "name": "Service Product",
                "type": "service",
                "is_published": True,
                "sale_ok": True,
            }
        )
        self.group_order.product_ids = [(4, service.id)]
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertIn(service, products)

    def test_confirmed_so_does_not_trigger_exclusion(self):
        """Un pedido confirmado (no draft) no reduce el stock neto para el filtro."""
        p = self._storable("Confirmed SO", stock=5.0)
        so = self._draft_so(p, 5.0)
        so.action_confirm()  # confirmado → no cuenta en draft_demand
        self.group_order.product_ids = [(4, p.id)]
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        # virtual_available ya incluye el movimiento de salida confirmado,
        # pero draft_demand es 0 → el filtro usa virtual_available sin descuento
        # El producto puede o no estar según el stock real, pero no se excluye por draft
        # En este test simplemente verificamos que no lanza error
        self.assertIsNotNone(products)

    def test_no_draft_demand_product_with_stock_included(self):
        """Producto sin ningún presupuesto draft y con stock positivo siempre incluido."""
        p = self._storable("No Demand", stock=8.0)
        self.group_order.product_ids = [(4, p.id)]
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertIn(p, products)

    def test_mixed_products_only_net_positive_included(self):
        """De varios productos, solo los con stock neto > 0 aparecen."""
        p_ok = self._storable("Has Stock", stock=10.0)
        p_ko = self._storable("No Stock", stock=2.0)
        self._draft_so(p_ko, 3.0)  # neto = -1
        self.group_order.product_ids = [
            (4, p_ok.id),
            (4, p_ko.id),
        ]
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertIn(p_ok, products)
        self.assertNotIn(p_ko, products)
