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

"""
Test suite for product discovery logic in website_sale_aplicoop.

The discovery mechanism uses 3 sources:
1. product_ids: Directly linked products
2. category_ids: Products from linked categories (recursive)
3. supplier_ids: Products from linked suppliers

Coverage:
- Correct union of all 3 sources (no duplicates)
- Deep category hierarchies (nested categories)
- Empty sources (empty categories/suppliers)
- Product filters (is_published, sale_ok)
- Ordering and deduplication
"""

from datetime import datetime
from datetime import timedelta

from odoo.tests.common import TransactionCase


class TestProductDiscoveryUnion(TransactionCase):
    """Test that product discovery returns correct union of 3 sources."""

    def setUp(self):
        super().setUp()
        self.group = self.env["res.partner"].create(
            {
                "name": "Test Group",
                "is_company": True,
            }
        )

        # Create a supplier
        self.supplier = self.env["res.partner"].create(
            {
                "name": "Test Supplier",
                "is_supplier": True,
            }
        )

        # Create categories
        self.category1 = self.env["product.category"].create(
            {
                "name": "Category 1",
            }
        )

        self.category2 = self.env["product.category"].create(
            {
                "name": "Category 2",
            }
        )

        # Create products
        # Direct product
        self.direct_product = self.env["product.product"].create(
            {
                "name": "Direct Product",
                "type": "consu",
                "list_price": 10.0,
                "is_published": True,
                "sale_ok": True,
            }
        )

        # Category 1 product
        self.cat1_product = self.env["product.product"].create(
            {
                "name": "Category 1 Product",
                "type": "consu",
                "list_price": 20.0,
                "categ_id": self.category1.id,
                "is_published": True,
                "sale_ok": True,
            }
        )

        # Category 2 product
        self.cat2_product = self.env["product.product"].create(
            {
                "name": "Category 2 Product",
                "type": "consu",
                "list_price": 30.0,
                "categ_id": self.category2.id,
                "is_published": True,
                "sale_ok": True,
            }
        )

        # Supplier product
        self.supplier_product = self.env["product.product"].create(
            {
                "name": "Supplier Product",
                "type": "consu",
                "list_price": 40.0,
                "categ_id": self.category1.id,  # Also in category
                "seller_ids": [
                    (
                        0,
                        0,
                        {
                            "partner_id": self.supplier.id,
                            "product_name": "Supplier Product",
                        },
                    )
                ],
                "is_published": True,
                "sale_ok": True,
            }
        )

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

    def test_discovery_from_direct_products(self):
        """Test discovery returns directly linked products."""
        self.group_order.product_ids = [(4, self.direct_product.id)]

        discovered = self.group_order.product_ids
        self.assertIn(self.direct_product, discovered)

    def test_discovery_from_categories(self):
        """Test discovery includes products from linked categories."""
        self.group_order.category_ids = [(4, self.category1.id)]

        # Computed placeholder to ensure discovery logic is exercised during test setup
        _ = self.group_order.product_ids
        # Should include cat1_product and supplier_product (both in category1)
        # Note: depends on how discovery is computed

    def test_discovery_from_suppliers(self):
        """Test discovery includes products from linked suppliers."""
        self.group_order.supplier_ids = [(4, self.supplier.id)]

        # Should include supplier_product
        # Note: depends on how supplier link is implemented

    def test_discovery_union_no_duplicates(self):
        """Test that union doesn't include same product twice."""
        # Add supplier_product via:
        # 1. Direct link
        # 2. Category link (cat1)
        # 3. Supplier link

        self.group_order.product_ids = [(4, self.supplier_product.id)]
        self.group_order.category_ids = [(4, self.category1.id)]
        self.group_order.supplier_ids = [(4, self.supplier.id)]

        discovered = self.group_order.product_ids

        # Count occurrences of supplier_product
        count = sum(1 for p in discovered if p == self.supplier_product)
        # Should appear only once
        self.assertEqual(count, 1)

    def test_discovery_filters_unpublished(self):
        """Test that unpublished products are excluded from discovery."""
        unpublished = self.env["product.product"].create(
            {
                "name": "Unpublished Product",
                "type": "consu",
                "list_price": 50.0,
                "categ_id": self.category1.id,
                "is_published": False,
                "sale_ok": True,
            }
        )

        self.group_order.category_ids = [(4, self.category1.id)]
        discovered = self.group_order.product_ids

        # Unpublished should not be in discovered
        self.assertNotIn(unpublished, discovered)

    def test_discovery_filters_not_for_sale(self):
        """Test that non-sellable products are excluded."""
        not_for_sale = self.env["product.product"].create(
            {
                "name": "Not For Sale",
                "type": "consu",
                "list_price": 60.0,
                "categ_id": self.category1.id,
                "is_published": True,
                "sale_ok": False,
            }
        )

        self.group_order.category_ids = [(4, self.category1.id)]
        discovered = self.group_order.product_ids

        # Not for sale should not be in discovered
        self.assertNotIn(not_for_sale, discovered)


class TestDeepCategoryHierarchies(TransactionCase):
    """Test product discovery with nested category structures."""

    def setUp(self):
        super().setUp()
        self.group = self.env["res.partner"].create(
            {
                "name": "Test Group",
                "is_company": True,
            }
        )

        # Create nested category structure:
        # Root -> L1 -> L2 -> L3 -> L4
        self.cat_l1 = self.env["product.category"].create(
            {
                "name": "Level 1",
            }
        )

        self.cat_l2 = self.env["product.category"].create(
            {
                "name": "Level 2",
                "parent_id": self.cat_l1.id,
            }
        )

        self.cat_l3 = self.env["product.category"].create(
            {
                "name": "Level 3",
                "parent_id": self.cat_l2.id,
            }
        )

        self.cat_l4 = self.env["product.category"].create(
            {
                "name": "Level 4",
                "parent_id": self.cat_l3.id,
            }
        )

        self.cat_l5 = self.env["product.category"].create(
            {
                "name": "Level 5",
                "parent_id": self.cat_l4.id,
            }
        )

        # Create products at each level
        self.product_l2 = self.env["product.product"].create(
            {
                "name": "Product L2",
                "type": "consu",
                "list_price": 10.0,
                "categ_id": self.cat_l2.id,
                "is_published": True,
                "sale_ok": True,
            }
        )

        self.product_l4 = self.env["product.product"].create(
            {
                "name": "Product L4",
                "type": "consu",
                "list_price": 20.0,
                "categ_id": self.cat_l4.id,
                "is_published": True,
                "sale_ok": True,
            }
        )

        self.product_l5 = self.env["product.product"].create(
            {
                "name": "Product L5",
                "type": "consu",
                "list_price": 30.0,
                "categ_id": self.cat_l5.id,
                "is_published": True,
                "sale_ok": True,
            }
        )

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

    def test_discovery_root_category_includes_all_descendants(self):
        """Test that linking root category discovers all nested products."""
        self.group_order.category_ids = [(4, self.cat_l1.id)]

        discovered = self.group_order.product_ids

        # Should include products from L2, L4, L5 (all descendants)
        self.assertIn(self.product_l2, discovered)
        self.assertIn(self.product_l4, discovered)
        self.assertIn(self.product_l5, discovered)

    def test_discovery_mid_level_category_includes_descendants(self):
        """Test discovery from middle of hierarchy."""
        self.group_order.category_ids = [(4, self.cat_l3.id)]

        discovered = self.group_order.product_ids

        # Should include L4 and L5 (descendants of L3)
        self.assertIn(self.product_l4, discovered)
        self.assertIn(self.product_l5, discovered)

        # Should not include L2 (ancestor)
        self.assertNotIn(self.product_l2, discovered)

    def test_discovery_leaf_category_only_own_products(self):
        """Test discovery from leaf (deepest) category."""
        self.group_order.category_ids = [(4, self.cat_l5.id)]

        discovered = self.group_order.product_ids

        # Should only include products directly in L5
        self.assertIn(self.product_l5, discovered)
        self.assertNotIn(self.product_l4, discovered)

    def test_discovery_circular_category_reference(self):
        """Test handling of circular category references (edge case)."""
        # Create circular reference (if allowed): L1 -> L2 -> L1
        # This should be prevented by Odoo constraints
        # or handled gracefully in discovery logic

        # Attempt to create circular ref may fail
        try:
            self.cat_l1.parent_id = self.cat_l5.id  # Creates loop
        except Exception as exc:
            # Expected: Odoo should prevent circular refs. Log for visibility.
            import logging

            logging.getLogger(__name__).info(
                "Expected exception creating circular category: %s", str(exc)
            )


class TestEmptySourcesDiscovery(TransactionCase):
    """Test discovery behavior with empty/null sources."""

    def setUp(self):
        super().setUp()
        self.group = self.env["res.partner"].create(
            {
                "name": "Test Group",
                "is_company": True,
            }
        )

        self.category = self.env["product.category"].create(
            {
                "name": "Empty Category",
            }
        )
        # No products in this category

        self.supplier = self.env["res.partner"].create(
            {
                "name": "Supplier No Products",
                "is_supplier": True,
            }
        )
        # No products from this supplier

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

    def test_discovery_empty_category(self):
        """Test discovery from empty category."""
        self.group_order.category_ids = [(4, self.category.id)]

        discovered = self.group_order.product_ids

        # Should return empty list
        self.assertEqual(len(discovered), 0)

    def test_discovery_empty_supplier(self):
        """Test discovery from supplier with no products."""
        self.group_order.supplier_ids = [(4, self.supplier.id)]

        discovered = self.group_order.product_ids

        # Should return empty list
        self.assertEqual(len(discovered), 0)

    def test_discovery_all_sources_empty(self):
        """Test when all 3 sources are empty."""
        # No direct products, empty category, empty supplier
        self.group_order.product_ids = [(6, 0, [])]
        self.group_order.category_ids = [(4, self.category.id)]
        self.group_order.supplier_ids = [(4, self.supplier.id)]

        discovered = self.group_order.product_ids

        # Should return empty
        self.assertEqual(len(discovered), 0)


class TestProductDiscoveryOrdering(TransactionCase):
    """Test that discovered products are returned in consistent order."""

    def setUp(self):
        super().setUp()
        self.group = self.env["res.partner"].create(
            {
                "name": "Test Group",
                "is_company": True,
            }
        )

        self.category = self.env["product.category"].create(
            {
                "name": "Test Category",
            }
        )

        # Create products with specific names
        self.products = []
        for i in range(5):
            product = self.env["product.product"].create(
                {
                    "name": f"Product {chr(65 + i)}",  # A, B, C, D, E
                    "type": "consu",
                    "list_price": (i + 1) * 10.0,
                    "categ_id": self.category.id,
                    "is_published": True,
                    "sale_ok": True,
                }
            )
            self.products.append(product)

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

    def test_discovery_consistent_ordering(self):
        """Test that repeated calls return same order."""
        self.group_order.category_ids = [(4, self.category.id)]

        discovered1 = list(self.group_order.product_ids)
        discovered2 = list(self.group_order.product_ids)

        # Order should be consistent
        self.assertEqual([p.id for p in discovered1], [p.id for p in discovered2])

    def test_discovery_alphabetical_or_price_order(self):
        """Test that products are ordered predictably."""
        self.group_order.category_ids = [(4, self.category.id)]

        discovered = list(self.group_order.product_ids)

        # Should be in some consistent order (name, price, ID, etc)
        # Verify they're the same products, regardless of order
        self.assertEqual(len(discovered), 5)
        discovered_ids = {p.id for p in discovered}
        expected_ids = {p.id for p in self.products}
        self.assertEqual(discovered_ids, expected_ids)


class TestProductBlacklist(TransactionCase):
    """Test blacklist (excluded_product_ids) functionality.

    The blacklist must have absolute priority over all inclusion sources:
    - Direct product_ids
    - Products from category_ids
    - Products from supplier_ids

    If a product is in excluded_product_ids, it should NEVER appear in
    the discovered products, regardless of how it was included.
    """

    def setUp(self):
        super().setUp()
        self.group = self.env["res.partner"].create(
            {
                "name": "Test Group",
                "is_company": True,
            }
        )

        # Create a supplier
        self.supplier = self.env["res.partner"].create(
            {
                "name": "Test Supplier",
                "is_company": True,
                "supplier_rank": 1,
            }
        )

        # Create category
        self.category = self.env["product.category"].create(
            {
                "name": "Test Category",
            }
        )

        # Create products
        # 1. Direct product (will be added to product_ids)
        self.direct_product = self.env["product.product"].create(
            {
                "name": "Direct Product",
                "type": "consu",
                "list_price": 10.0,
                "is_published": True,
                "sale_ok": True,
            }
        )

        # 2. Category product (will be included via category_ids)
        self.category_product = self.env["product.product"].create(
            {
                "name": "Category Product",
                "type": "consu",
                "list_price": 20.0,
                "categ_id": self.category.id,
                "is_published": True,
                "sale_ok": True,
            }
        )

        # 3. Supplier product (will be included via supplier_ids)
        product_tmpl = self.env["product.template"].create(
            {
                "name": "Supplier Product",
                "type": "consu",
                "list_price": 30.0,
                "is_published": True,
                "sale_ok": True,
            }
        )
        self.supplier_product = product_tmpl.product_variant_ids[0]
        self.env["product.supplierinfo"].create(
            {
                "partner_id": self.supplier.id,
                "product_tmpl_id": product_tmpl.id,
                "price": 25.0,
            }
        )

        # 4. Multi-source product (in all three sources)
        multi_tmpl = self.env["product.template"].create(
            {
                "name": "Multi Source Product",
                "type": "consu",
                "list_price": 40.0,
                "categ_id": self.category.id,
                "is_published": True,
                "sale_ok": True,
            }
        )
        self.multi_product = multi_tmpl.product_variant_ids[0]
        self.env["product.supplierinfo"].create(
            {
                "partner_id": self.supplier.id,
                "product_tmpl_id": multi_tmpl.id,
                "price": 35.0,
            }
        )

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

    def test_blacklist_excludes_direct_product(self):
        """Test that excluded_product_ids filters out directly linked products."""
        # Add product directly
        self.group_order.product_ids = [(4, self.direct_product.id)]

        # Product should be discoverable
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertIn(self.direct_product, products)

        # Now add to blacklist
        self.group_order.excluded_product_ids = [(4, self.direct_product.id)]

        # Product should NOT be discoverable anymore
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertNotIn(self.direct_product, products)

    def test_blacklist_excludes_category_product(self):
        """Test that excluded_product_ids filters out products from categories."""
        # Add category (includes category_product)
        self.group_order.category_ids = [(4, self.category.id)]

        # Product should be discoverable
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertIn(self.category_product, products)

        # Now add to blacklist
        self.group_order.excluded_product_ids = [(4, self.category_product.id)]

        # Product should NOT be discoverable anymore
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertNotIn(self.category_product, products)

    def test_blacklist_excludes_supplier_product(self):
        """Test that excluded_product_ids filters out products from suppliers."""
        # Add supplier (includes supplier_product)
        self.group_order.supplier_ids = [(4, self.supplier.id)]

        # Product should be discoverable
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertIn(self.supplier_product, products)

        # Now add to blacklist
        self.group_order.excluded_product_ids = [(4, self.supplier_product.id)]

        # Product should NOT be discoverable anymore
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertNotIn(self.supplier_product, products)

    def test_blacklist_priority_over_all_sources(self):
        """Test that blacklist has absolute priority even for multi-source products."""
        # Add multi_product via all three sources
        self.group_order.product_ids = [(4, self.multi_product.id)]
        self.group_order.category_ids = [(4, self.category.id)]
        self.group_order.supplier_ids = [(4, self.supplier.id)]

        # Product should be discoverable
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertIn(self.multi_product, products)

        # Now add to blacklist
        self.group_order.excluded_product_ids = [(4, self.multi_product.id)]

        # Product should NOT be discoverable anymore, despite being in all sources
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertNotIn(self.multi_product, products)

    def test_empty_blacklist_no_effect(self):
        """Test that empty excluded_product_ids doesn't affect discovery."""
        # Add products via various sources
        self.group_order.product_ids = [(4, self.direct_product.id)]
        self.group_order.category_ids = [(4, self.category.id)]
        self.group_order.supplier_ids = [(4, self.supplier.id)]

        # All products should be discoverable
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertIn(self.direct_product, products)
        self.assertIn(self.category_product, products)
        self.assertIn(self.supplier_product, products)

        # Excluded list is empty - should have no effect
        self.assertEqual(len(self.group_order.excluded_product_ids), 0)

    def test_blacklist_multiple_products(self):
        """Test excluding multiple products at once."""
        # Add all products
        self.group_order.product_ids = [(4, self.direct_product.id)]
        self.group_order.category_ids = [(4, self.category.id)]
        self.group_order.supplier_ids = [(4, self.supplier.id)]

        # Exclude two products
        self.group_order.excluded_product_ids = [
            (4, self.direct_product.id),
            (4, self.category_product.id),
        ]

        products = self.group_order._get_products_for_group_order(self.group_order.id)

        # These two should NOT be in results
        self.assertNotIn(self.direct_product, products)
        self.assertNotIn(self.category_product, products)

        # But supplier_product should still be there
        self.assertIn(self.supplier_product, products)

    def test_blacklist_available_products_count(self):
        """Test that available_products_count reflects blacklist."""
        # Add products
        self.group_order.product_ids = [(4, self.direct_product.id)]
        self.group_order.category_ids = [(4, self.category.id)]

        # Count should include direct + category products
        initial_count = self.group_order.available_products_count
        self.assertGreater(initial_count, 0)

        # Exclude one product
        self.group_order.excluded_product_ids = [(4, self.category_product.id)]

        # Count should decrease by 1
        new_count = self.group_order.available_products_count
        self.assertEqual(new_count, initial_count - 1)


class TestSupplierBlacklist(TransactionCase):
    """Test supplier blacklist (excluded_supplier_ids) functionality.

    The supplier blacklist filters out products whose main_seller_id
    (from product_main_seller addon) is in the excluded suppliers list.

    Blacklist has absolute priority over inclusion sources.
    """

    def setUp(self):
        super().setUp()
        self.group = self.env["res.partner"].create(
            {
                "name": "Test Group",
                "is_company": True,
            }
        )

        # Create suppliers
        self.supplier_A = self.env["res.partner"].create(
            {
                "name": "Supplier A",
                "is_company": True,
                "supplier_rank": 1,
            }
        )

        self.supplier_B = self.env["res.partner"].create(
            {
                "name": "Supplier B",
                "is_company": True,
                "supplier_rank": 1,
            }
        )

        self.supplier_C = self.env["res.partner"].create(
            {
                "name": "Supplier C",
                "is_company": True,
                "supplier_rank": 1,
            }
        )

        # Create category
        self.category = self.env["product.category"].create(
            {
                "name": "Test Category",
            }
        )

        # Create products with different main sellers
        # Product 1: main seller = Supplier A
        tmpl_1 = self.env["product.template"].create(
            {
                "name": "Product from Supplier A",
                "type": "consu",
                "list_price": 10.0,
                "categ_id": self.category.id,
                "is_published": True,
                "sale_ok": True,
                "main_seller_id": self.supplier_A.id,
            }
        )
        self.product_A = tmpl_1.product_variant_ids[0]

        # Product 2: main seller = Supplier B
        tmpl_2 = self.env["product.template"].create(
            {
                "name": "Product from Supplier B",
                "type": "consu",
                "list_price": 20.0,
                "categ_id": self.category.id,
                "is_published": True,
                "sale_ok": True,
                "main_seller_id": self.supplier_B.id,
            }
        )
        self.product_B = tmpl_2.product_variant_ids[0]

        # Product 3: main seller = Supplier C
        tmpl_3 = self.env["product.template"].create(
            {
                "name": "Product from Supplier C",
                "type": "consu",
                "list_price": 30.0,
                "categ_id": self.category.id,
                "is_published": True,
                "sale_ok": True,
                "main_seller_id": self.supplier_C.id,
            }
        )
        self.product_C = tmpl_3.product_variant_ids[0]

        # Product 4: no main seller
        tmpl_4 = self.env["product.template"].create(
            {
                "name": "Product without main seller",
                "type": "consu",
                "list_price": 40.0,
                "categ_id": self.category.id,
                "is_published": True,
                "sale_ok": True,
            }
        )
        self.product_no_seller = tmpl_4.product_variant_ids[0]

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

    def test_supplier_blacklist_excludes_by_main_seller(self):
        """Test that supplier blacklist excludes products by main_seller_id."""
        # Add all products via category
        self.group_order.category_ids = [(4, self.category.id)]

        # All products should be discoverable initially
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertIn(self.product_A, products)
        self.assertIn(self.product_B, products)
        self.assertIn(self.product_C, products)
        self.assertIn(self.product_no_seller, products)

        # Exclude Supplier A
        self.group_order.excluded_supplier_ids = [(4, self.supplier_A.id)]

        # Product A should NOT be discoverable anymore
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertNotIn(self.product_A, products)
        self.assertIn(self.product_B, products)
        self.assertIn(self.product_C, products)
        self.assertIn(self.product_no_seller, products)

    def test_supplier_blacklist_multiple_suppliers(self):
        """Test excluding multiple suppliers at once."""
        # Add all products via category
        self.group_order.category_ids = [(4, self.category.id)]

        # Exclude Suppliers A and B
        self.group_order.excluded_supplier_ids = [
            (4, self.supplier_A.id),
            (4, self.supplier_B.id),
        ]

        products = self.group_order._get_products_for_group_order(self.group_order.id)

        # Products A and B should NOT be in results
        self.assertNotIn(self.product_A, products)
        self.assertNotIn(self.product_B, products)

        # But product C and no-seller product should be there
        self.assertIn(self.product_C, products)
        self.assertIn(self.product_no_seller, products)

    def test_supplier_blacklist_does_not_affect_no_main_seller(self):
        """Test that products without main_seller_id are not affected by supplier blacklist."""
        # Add all products via category
        self.group_order.category_ids = [(4, self.category.id)]

        # Exclude all suppliers
        self.group_order.excluded_supplier_ids = [
            (4, self.supplier_A.id),
            (4, self.supplier_B.id),
            (4, self.supplier_C.id),
        ]

        products = self.group_order._get_products_for_group_order(self.group_order.id)

        # Products with main sellers should NOT be in results
        self.assertNotIn(self.product_A, products)
        self.assertNotIn(self.product_B, products)
        self.assertNotIn(self.product_C, products)

        # But product without main seller should still be there
        self.assertIn(self.product_no_seller, products)

    def test_supplier_blacklist_with_direct_product_inclusion(self):
        """Test that supplier blacklist affects even directly included products."""
        # Add product A directly
        self.group_order.product_ids = [(4, self.product_A.id)]

        # Product should be discoverable
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertIn(self.product_A, products)

        # Exclude Supplier A
        self.group_order.excluded_supplier_ids = [(4, self.supplier_A.id)]

        # Product A should NOT be discoverable anymore, even though directly included
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertNotIn(self.product_A, products)

    def test_supplier_blacklist_with_supplier_inclusion(self):
        """Test that supplier blacklist has priority over supplier inclusion."""
        # Add Supplier A to included suppliers
        self.group_order.supplier_ids = [(4, self.supplier_A.id)]

        # Also add Supplier A to excluded suppliers (blacklist has priority)
        self.group_order.excluded_supplier_ids = [(4, self.supplier_A.id)]

        products = self.group_order._get_products_for_group_order(self.group_order.id)

        # Product A should NOT be discoverable (blacklist wins)
        self.assertNotIn(self.product_A, products)

    def test_empty_supplier_blacklist_no_effect(self):
        """Test that empty excluded_supplier_ids doesn't affect discovery."""
        # Add products via category
        self.group_order.category_ids = [(4, self.category.id)]

        # All products should be discoverable
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertIn(self.product_A, products)
        self.assertIn(self.product_B, products)
        self.assertIn(self.product_C, products)

        # Excluded supplier list is empty - should have no effect
        self.assertEqual(len(self.group_order.excluded_supplier_ids), 0)

    def test_supplier_and_product_blacklist_combined(self):
        """Test that both product and supplier blacklists work together."""
        # Add all products via category
        self.group_order.category_ids = [(4, self.category.id)]

        # Exclude Supplier A (affects product_A)
        self.group_order.excluded_supplier_ids = [(4, self.supplier_A.id)]

        # Exclude product B directly
        self.group_order.excluded_product_ids = [(4, self.product_B.id)]

        products = self.group_order._get_products_for_group_order(self.group_order.id)

        # Products A and B should NOT be in results
        self.assertNotIn(self.product_A, products)
        self.assertNotIn(self.product_B, products)

        # But products C and no-seller should be there
        self.assertIn(self.product_C, products)
        self.assertIn(self.product_no_seller, products)

    def test_supplier_blacklist_available_products_count(self):
        """Test that available_products_count reflects supplier blacklist."""
        # Add products
        self.group_order.category_ids = [(4, self.category.id)]

        # Count should include all 4 products
        initial_count = self.group_order.available_products_count
        self.assertEqual(initial_count, 4)

        # Exclude Supplier A
        self.group_order.excluded_supplier_ids = [(4, self.supplier_A.id)]

        # Count should decrease by 1 (product_A excluded)
        new_count = self.group_order.available_products_count
        self.assertEqual(new_count, 3)


class TestCategoryBlacklist(TransactionCase):
    """Test category blacklist (excluded_category_ids) functionality.

    The category blacklist filters out products in the excluded categories
    AND all their subcategories (recursive).

    Blacklist has absolute priority over inclusion sources.
    """

    def setUp(self):
        super().setUp()
        self.group = self.env["res.partner"].create(
            {
                "name": "Test Group",
                "is_company": True,
            }
        )

        # Create category hierarchy:
        # Parent Category
        # ├── Child Category A
        # │   └── Grandchild Category A1
        # └── Child Category B
        self.parent_category = self.env["product.category"].create(
            {
                "name": "Parent Category",
            }
        )

        self.child_category_A = self.env["product.category"].create(
            {
                "name": "Child Category A",
                "parent_id": self.parent_category.id,
            }
        )

        self.grandchild_category_A1 = self.env["product.category"].create(
            {
                "name": "Grandchild Category A1",
                "parent_id": self.child_category_A.id,
            }
        )

        self.child_category_B = self.env["product.category"].create(
            {
                "name": "Child Category B",
                "parent_id": self.parent_category.id,
            }
        )

        self.other_category = self.env["product.category"].create(
            {
                "name": "Other Category (not in hierarchy)",
            }
        )

        # Create products in different categories
        # Product in parent category
        self.product_parent = self.env["product.product"].create(
            {
                "name": "Product in Parent Category",
                "type": "consu",
                "list_price": 10.0,
                "categ_id": self.parent_category.id,
                "is_published": True,
                "sale_ok": True,
            }
        )

        # Product in Child A
        self.product_child_A = self.env["product.product"].create(
            {
                "name": "Product in Child A",
                "type": "consu",
                "list_price": 20.0,
                "categ_id": self.child_category_A.id,
                "is_published": True,
                "sale_ok": True,
            }
        )

        # Product in Grandchild A1
        self.product_grandchild_A1 = self.env["product.product"].create(
            {
                "name": "Product in Grandchild A1",
                "type": "consu",
                "list_price": 30.0,
                "categ_id": self.grandchild_category_A1.id,
                "is_published": True,
                "sale_ok": True,
            }
        )

        # Product in Child B
        self.product_child_B = self.env["product.product"].create(
            {
                "name": "Product in Child B",
                "type": "consu",
                "list_price": 40.0,
                "categ_id": self.child_category_B.id,
                "is_published": True,
                "sale_ok": True,
            }
        )

        # Product in Other Category
        self.product_other = self.env["product.product"].create(
            {
                "name": "Product in Other Category",
                "type": "consu",
                "list_price": 50.0,
                "categ_id": self.other_category.id,
                "is_published": True,
                "sale_ok": True,
            }
        )

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

    def test_category_blacklist_excludes_single_category(self):
        """Test that category blacklist excludes products in that category."""
        # Add parent category to inclusion (includes all products in hierarchy)
        self.group_order.category_ids = [(4, self.parent_category.id)]

        # All products should be discoverable initially
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertIn(self.product_parent, products)
        self.assertIn(self.product_child_A, products)
        self.assertIn(self.product_grandchild_A1, products)
        self.assertIn(self.product_child_B, products)

        # Exclude Child Category B
        self.group_order.excluded_category_ids = [(4, self.child_category_B.id)]

        # Product in Child B should NOT be discoverable
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertNotIn(self.product_child_B, products)
        # But others should still be there
        self.assertIn(self.product_parent, products)
        self.assertIn(self.product_child_A, products)
        self.assertIn(self.product_grandchild_A1, products)

    def test_category_blacklist_excludes_with_subcategories(self):
        """Test that excluding a category also excludes all its subcategories (recursive)."""
        # Add parent category to inclusion
        self.group_order.category_ids = [(4, self.parent_category.id)]

        # Exclude Child Category A (should also exclude Grandchild A1)
        self.group_order.excluded_category_ids = [(4, self.child_category_A.id)]

        products = self.group_order._get_products_for_group_order(self.group_order.id)

        # Products in Child A and Grandchild A1 should NOT be discoverable
        self.assertNotIn(self.product_child_A, products)
        self.assertNotIn(self.product_grandchild_A1, products)  # Recursive exclusion

        # But products in parent and Child B should still be there
        self.assertIn(self.product_parent, products)
        self.assertIn(self.product_child_B, products)

    def test_category_blacklist_excludes_parent_excludes_all_children(self):
        """Test that excluding parent category excludes ALL descendants."""
        # Add parent category to inclusion (includes all)
        self.group_order.category_ids = [(4, self.parent_category.id)]

        # Exclude the parent category (should exclude ALL products in hierarchy)
        self.group_order.excluded_category_ids = [(4, self.parent_category.id)]

        products = self.group_order._get_products_for_group_order(self.group_order.id)

        # ALL products in the hierarchy should be excluded
        self.assertNotIn(self.product_parent, products)
        self.assertNotIn(self.product_child_A, products)
        self.assertNotIn(self.product_grandchild_A1, products)
        self.assertNotIn(self.product_child_B, products)

        # Result should be empty (no products available)
        self.assertEqual(len(products), 0)

    def test_category_blacklist_with_direct_product_inclusion(self):
        """Test that category blacklist affects even directly included products."""
        # Add product directly
        self.group_order.product_ids = [(4, self.product_child_A.id)]

        # Product should be discoverable
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertIn(self.product_child_A, products)

        # Exclude the category
        self.group_order.excluded_category_ids = [(4, self.child_category_A.id)]

        # Product should NOT be discoverable anymore, even though directly included
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertNotIn(self.product_child_A, products)

    def test_category_blacklist_does_not_affect_other_categories(self):
        """Test that excluding a category doesn't affect products in unrelated categories."""
        # Add all categories to inclusion
        self.group_order.category_ids = [
            (4, self.parent_category.id),
            (4, self.other_category.id),
        ]

        # Exclude parent category
        self.group_order.excluded_category_ids = [(4, self.parent_category.id)]

        products = self.group_order._get_products_for_group_order(self.group_order.id)

        # Products in parent hierarchy should NOT be there
        self.assertNotIn(self.product_parent, products)
        self.assertNotIn(self.product_child_A, products)
        self.assertNotIn(self.product_child_B, products)

        # But product in other category should still be there
        self.assertIn(self.product_other, products)

    def test_empty_category_blacklist_no_effect(self):
        """Test that empty excluded_category_ids doesn't affect discovery."""
        # Add parent category
        self.group_order.category_ids = [(4, self.parent_category.id)]

        # All products should be discoverable
        products = self.group_order._get_products_for_group_order(self.group_order.id)
        self.assertIn(self.product_parent, products)
        self.assertIn(self.product_child_A, products)
        self.assertIn(self.product_grandchild_A1, products)
        self.assertIn(self.product_child_B, products)

        # Excluded category list is empty - should have no effect
        self.assertEqual(len(self.group_order.excluded_category_ids), 0)

    def test_multiple_category_exclusions(self):
        """Test excluding multiple categories at once."""
        # Add parent category
        self.group_order.category_ids = [(4, self.parent_category.id)]

        # Exclude both child categories
        self.group_order.excluded_category_ids = [
            (4, self.child_category_A.id),
            (4, self.child_category_B.id),
        ]

        products = self.group_order._get_products_for_group_order(self.group_order.id)

        # Products in both child categories (+ grandchild) should NOT be there
        self.assertNotIn(self.product_child_A, products)
        self.assertNotIn(self.product_grandchild_A1, products)
        self.assertNotIn(self.product_child_B, products)

        # But product in parent category should still be there
        self.assertIn(self.product_parent, products)

    def test_category_blacklist_combined_with_other_blacklists(self):
        """Test that category blacklist works together with product and supplier blacklists."""
        # Add parent category
        self.group_order.category_ids = [(4, self.parent_category.id)]

        # Exclude Child A category (affects product_child_A and product_grandchild_A1)
        self.group_order.excluded_category_ids = [(4, self.child_category_A.id)]

        # Also exclude product_child_B directly
        self.group_order.excluded_product_ids = [(4, self.product_child_B.id)]

        products = self.group_order._get_products_for_group_order(self.group_order.id)

        # Products excluded by category blacklist
        self.assertNotIn(self.product_child_A, products)
        self.assertNotIn(self.product_grandchild_A1, products)

        # Product excluded by product blacklist
        self.assertNotIn(self.product_child_B, products)

        # Only product_parent should be available
        self.assertIn(self.product_parent, products)
        self.assertEqual(len(products), 1)

    def test_category_blacklist_available_products_count(self):
        """Test that available_products_count reflects category blacklist."""
        # Add parent category (4 products in hierarchy)
        self.group_order.category_ids = [(4, self.parent_category.id)]

        # Count should include all 4 products
        initial_count = self.group_order.available_products_count
        self.assertEqual(initial_count, 4)

        # Exclude Child Category A (should exclude 2 products: child_A + grandchild_A1)
        self.group_order.excluded_category_ids = [(4, self.child_category_A.id)]

        # Count should decrease by 2
        new_count = self.group_order.available_products_count
        self.assertEqual(new_count, 2)
