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

from datetime import timedelta

from odoo import fields
from odoo.tests.common import TransactionCase
from odoo.tests.common import tagged


@tagged("post_install", "date_calculations")
class TestDateCalculations(TransactionCase):
    """Test suite for date calculation methods in group.order model."""

    def setUp(self):
        super().setUp()
        # Create a test group
        self.group = self.env["res.partner"].create(
            {
                "name": "Test Group",
                "is_company": True,
                "email": "group@test.com",
            }
        )

    def test_compute_pickup_date_basic(self):
        """Test pickup_date calculation returns next occurrence of pickup day."""
        # Use today as reference and calculate next Tuesday
        today = fields.Date.today()
        # Find next Sunday (weekday 6) from today
        days_until_sunday = (6 - today.weekday()) % 7
        if days_until_sunday == 0:  # If today is Sunday
            start_date = today
        else:
            start_date = today + timedelta(days=days_until_sunday)

        # Create order with pickup_day = Tuesday (1), starting on Sunday
        # NO cutoff_day to avoid dependency on cutoff_date
        order = self.env["group.order"].create(
            {
                "name": "Test Order",
                "group_ids": [(6, 0, [self.group.id])],
                "start_date": start_date,  # Sunday
                "pickup_day": "1",  # Tuesday
                "cutoff_day": False,  # Disable to avoid cutoff_date interference
            }
        )

        # Force computation
        order._compute_pickup_date()

        # Expected: Next Tuesday after Sunday (2 days later)
        expected_date = start_date + timedelta(days=2)
        self.assertEqual(
            order.pickup_date,
            expected_date,
            f"Expected {expected_date}, got {order.pickup_date}",
        )

    def test_compute_pickup_date_same_day(self):
        """Test pickup_date when start_date is same weekday as pickup_day."""
        # Find next Tuesday from today
        today = fields.Date.today()
        days_until_tuesday = (1 - today.weekday()) % 7
        if days_until_tuesday == 0:  # If today is Tuesday
            start_date = today
        else:
            start_date = today + timedelta(days=days_until_tuesday)

        # Start on Tuesday, pickup also Tuesday - should return next week's Tuesday
        order = self.env["group.order"].create(
            {
                "name": "Test Order Same Day",
                "group_ids": [(6, 0, [self.group.id])],
                "start_date": start_date,  # Tuesday
                "pickup_day": "1",  # Tuesday
            }
        )

        order._compute_pickup_date()

        # Should get next Tuesday (7 days later)
        expected_date = start_date + timedelta(days=7)
        self.assertEqual(order.pickup_date, expected_date)

    def test_compute_pickup_date_no_start_date(self):
        """Test pickup_date calculation when no start_date is set."""
        order = self.env["group.order"].create(
            {
                "name": "Test Order No Start",
                "group_ids": [(6, 0, [self.group.id])],
                "start_date": False,
                "pickup_day": "1",  # Tuesday
            }
        )

        order._compute_pickup_date()

        # Should calculate from today
        self.assertIsNotNone(order.pickup_date)
        # Verify it's a future date and falls on Tuesday
        self.assertGreaterEqual(order.pickup_date, fields.Date.today())
        self.assertEqual(order.pickup_date.weekday(), 1)  # 1 = Tuesday

    def test_compute_pickup_date_without_pickup_day(self):
        """Test pickup_date is None when pickup_day is not set."""
        order = self.env["group.order"].create(
            {
                "name": "Test Order No Pickup Day",
                "group_ids": [(6, 0, [self.group.id])],
                "start_date": fields.Date.today(),
                "pickup_day": False,
            }
        )

        order._compute_pickup_date()
        # In Odoo, computed Date fields return False (not None) when no value
        self.assertFalse(order.pickup_date)

    def test_compute_pickup_date_all_weekdays(self):
        """Test pickup_date calculation for each day of the week."""
        base_date = fields.Date.from_string("2026-02-02")  # Monday

        for day_num in range(7):
            day_name = [
                "Monday",
                "Tuesday",
                "Wednesday",
                "Thursday",
                "Friday",
                "Saturday",
                "Sunday",
            ][day_num]

            order = self.env["group.order"].create(
                {
                    "name": f"Test Order {day_name}",
                    "group_ids": [(6, 0, [self.group.id])],
                    "start_date": base_date,
                    "pickup_day": str(day_num),
                }
            )

            order._compute_pickup_date()

            # Verify the weekday matches
            self.assertEqual(
                order.pickup_date.weekday(),
                day_num,
                f"Pickup date weekday should be {day_num} ({day_name})",
            )

            # Verify it's after start_date
            self.assertGreater(order.pickup_date, base_date)

    def test_compute_delivery_date_basic(self):
        """Test delivery_date is pickup_date + 1 day."""
        # Find next Sunday from today
        today = fields.Date.today()
        days_until_sunday = (6 - today.weekday()) % 7
        if days_until_sunday == 0:  # If today is Sunday
            start_date = today
        else:
            start_date = today + timedelta(days=days_until_sunday)

        order = self.env["group.order"].create(
            {
                "name": "Test Delivery Date",
                "group_ids": [(6, 0, [self.group.id])],
                "start_date": start_date,  # Sunday
                "pickup_day": "1",  # Tuesday = start_date + 2 days
            }
        )

        order._compute_pickup_date()
        order._compute_delivery_date()

        # Pickup is Tuesday (2 days after Sunday start_date)
        expected_pickup = start_date + timedelta(days=2)
        # Delivery should be Wednesday (Tuesday + 1)
        expected_delivery = expected_pickup + timedelta(days=1)
        self.assertEqual(order.delivery_date, expected_delivery)

    def test_compute_delivery_date_without_pickup(self):
        """Test delivery_date is None when pickup_date is not set."""
        order = self.env["group.order"].create(
            {
                "name": "Test No Delivery",
                "group_ids": [(6, 0, [self.group.id])],
                "start_date": fields.Date.today(),
                "pickup_day": False,  # No pickup day = no pickup_date
            }
        )

        order._compute_pickup_date()
        order._compute_delivery_date()

        # In Odoo, computed Date fields return False (not None) when no value
        self.assertFalse(order.delivery_date)

    def test_compute_cutoff_date_basic(self):
        """Test cutoff_date calculation returns next occurrence of cutoff day."""
        # Create order with cutoff_day = Sunday (6)
        # If today is Sunday, cutoff should be today (days_ahead = 0 is allowed)
        order = self.env["group.order"].create(
            {
                "name": "Test Cutoff Date",
                "group_ids": [(6, 0, [self.group.id])],
                "start_date": fields.Date.from_string("2026-02-01"),  # Sunday
                "cutoff_day": "6",  # Sunday
            }
        )

        order._compute_cutoff_date()

        # When today (in code) matches cutoff_day, days_ahead=0, so cutoff is today
        # The function uses datetime.now().date(), so we can't predict exact date
        # Instead verify: cutoff_date is set and falls on correct weekday
        self.assertIsNotNone(order.cutoff_date)
        self.assertEqual(order.cutoff_date.weekday(), 6)  # Sunday

    def test_compute_cutoff_date_without_cutoff_day(self):
        """Test cutoff_date is None when cutoff_day is not set."""
        order = self.env["group.order"].create(
            {
                "name": "Test No Cutoff",
                "group_ids": [(6, 0, [self.group.id])],
                "start_date": fields.Date.today(),
                "cutoff_day": False,
            }
        )

        order._compute_cutoff_date()
        # In Odoo, computed Date fields return False (not None) when no value
        self.assertFalse(order.cutoff_date)

    def test_date_dependency_chain(self):
        """Test that changing start_date triggers recomputation of date fields."""
        # Find next Sunday from today
        today = fields.Date.today()
        days_until_sunday = (6 - today.weekday()) % 7
        if days_until_sunday == 0:  # If today is Sunday
            start_date = today
        else:
            start_date = today + timedelta(days=days_until_sunday)

        order = self.env["group.order"].create(
            {
                "name": "Test Date Chain",
                "group_ids": [(6, 0, [self.group.id])],
                "start_date": start_date,  # Dynamic Sunday
                "pickup_day": "6",  # Sunday (must be >= cutoff_day)
                "cutoff_day": "5",  # Saturday
            }
        )

        # Get initial dates
        initial_pickup = order.pickup_date
        initial_delivery = order.delivery_date
        # Note: cutoff_date uses datetime.now() not start_date, so won't change

        # Change start_date to a week later
        new_start_date = start_date + timedelta(days=7)
        order.write({"start_date": new_start_date})

        # Verify pickup and delivery dates changed
        self.assertNotEqual(order.pickup_date, initial_pickup)
        self.assertNotEqual(order.delivery_date, initial_delivery)

        # Verify dates are still consistent
        if order.pickup_date and order.delivery_date:
            delta = order.delivery_date - order.pickup_date
            self.assertEqual(delta.days, 1)

    def test_pickup_date_no_extra_week_bug(self):
        """Regression test: ensure pickup_date doesn't add extra week incorrectly.

        Bug context: Previously when cutoff_day >= pickup_day numerically,
        logic incorrectly added 7 extra days even when pickup was already
        ahead in the calendar.
        """
        # Scenario: Pickup Tuesday (1)
        # Start: Sunday (dynamic)
        # Expected pickup: Tuesday (2 days later, NOT +9 days)
        # NOTE: NO cutoff_day to avoid cutoff_date dependency

        # Find next Sunday from today
        today = fields.Date.today()
        days_until_sunday = (6 - today.weekday()) % 7
        if days_until_sunday == 0:  # If today is Sunday
            start_date = today
        else:
            start_date = today + timedelta(days=days_until_sunday)

        order = self.env["group.order"].create(
            {
                "name": "Regression Test Extra Week",
                "group_ids": [(6, 0, [self.group.id])],
                "start_date": start_date,  # Sunday (dynamic)
                "pickup_day": "1",  # Tuesday (numerically < 6)
                "cutoff_day": False,  # Disable to test pure start_date logic
            }
        )

        order._compute_pickup_date()

        # Must be 2 days after start_date (Tuesday)
        expected = start_date + timedelta(days=2)
        self.assertEqual(
            order.pickup_date,
            expected,
            f"Bug detected: pickup_date should be {expected} not {order.pickup_date}",
        )

        # Verify it's exactly 2 days after start_date
        delta = order.pickup_date - order.start_date
        self.assertEqual(
            delta.days, 2, "Pickup should be 2 days after Sunday start_date"
        )

    def test_multiple_orders_same_pickup_day(self):
        """Test multiple orders with same pickup day get consistent dates."""
        start = fields.Date.from_string("2026-02-01")
        pickup_day = "1"  # Tuesday

        orders = []
        for i in range(3):
            order = self.env["group.order"].create(
                {
                    "name": f"Test Order {i}",
                    "group_ids": [(6, 0, [self.group.id])],
                    "start_date": start,
                    "pickup_day": pickup_day,
                }
            )
            orders.append(order)

        # All should have same pickup_date
        pickup_dates = [o.pickup_date for o in orders]
        self.assertEqual(
            len(set(pickup_dates)),
            1,
            "All orders with same start_date and pickup_day should have same pickup_date",
        )

    # === NEW REGRESSION TESTS (v18.0.1.3.1) ===

    def test_cutoff_same_day_as_today_bug_fix(self):
        """Regression test: cutoff_date should allow same day as today.

        Bug fixed in v18.0.1.3.1: Previously, if cutoff_day == today.weekday(),
        the system would incorrectly add 7 days, scheduling cutoff for next week.
        Now cutoff_date can be today if cutoff_day matches today's weekday.
        """
        today = fields.Date.today()
        cutoff_day = str(today.weekday())  # Same as today

        order = self.env["group.order"].create(
            {
                "name": "Test Cutoff Today",
                "group_ids": [(6, 0, [self.group.id])],
                "start_date": today,
                "cutoff_day": cutoff_day,
                "period": "weekly",
            }
        )

        # cutoff_date should be TODAY, not next week
        self.assertEqual(
            order.cutoff_date,
            today,
            f"Expected cutoff_date={today} (today), got {order.cutoff_date}. "
            "Cutoff should be allowed on the same day.",
        )

    def test_delivery_date_stored_correctly(self):
        """Regression test: delivery_date must be stored in database.

        Bug fixed in v18.0.1.3.1: delivery_date had store=False, causing
        inconsistent values and inability to search/filter by this field.
        Now delivery_date is stored (store=True).
        """
        today = fields.Date.today()
        # Set pickup for next Monday
        days_until_monday = (0 - today.weekday()) % 7
        if days_until_monday == 0:
            days_until_monday = 7
        start_date = today + timedelta(days=days_until_monday - 1)

        order = self.env["group.order"].create(
            {
                "name": "Test Delivery Stored",
                "group_ids": [(6, 0, [self.group.id])],
                "start_date": start_date,
                "pickup_day": "0",  # Monday
                "period": "weekly",
            }
        )

        # Force computation
        order._compute_pickup_date()
        order._compute_delivery_date()

        expected_delivery = order.pickup_date + timedelta(days=1)
        self.assertEqual(
            order.delivery_date,
            expected_delivery,
            f"Expected delivery_date={expected_delivery}, got {order.delivery_date}",
        )

        # Verify it's stored: read from database
        order_from_db = self.env["group.order"].browse(order.id)
        self.assertEqual(
            order_from_db.delivery_date,
            expected_delivery,
            "delivery_date should be persisted in database (store=True)",
        )

    def test_constraint_cutoff_before_pickup_invalid(self):
        """Test that any combination of cutoff_day and pickup_day is allowed.

        As of v18.0.1.3.1, the constraint has been removed to allow flexibility
        in scheduling. Pickup and cutoff days can now be in any order.

        This test verifies that no ValidationError is raised even when
        pickup_day < cutoff_day numerically.
        """
        today = fields.Date.today()

        # This configuration is now allowed (previously would raise ValidationError)
        # pickup (Tuesday=1) < cutoff (Thursday=3)
        order = self.env["group.order"].create(
            {
                "name": "Order with earlier pickup than cutoff",
                "group_ids": [(6, 0, [self.group.id])],
                "start_date": today,
                "cutoff_day": "3",  # Thursday
                "pickup_day": "1",  # Tuesday (numerically before Thursday)
                "period": "weekly",
            }
        )
        self.assertIsNotNone(order, "Order should be created without constraint error")

    def test_constraint_cutoff_before_pickup_valid(self):
        """Test constraint allows valid configurations.

        Valid scenarios:
        - pickup_day > cutoff_day: pickup is after cutoff ✓
        - pickup_day == cutoff_day: same day allowed ✓
        """
        today = fields.Date.today()

        # Valid: pickup (Saturday=5) > cutoff (Tuesday=1)
        order1 = self.env["group.order"].create(
            {
                "name": "Valid Order 1",
                "group_ids": [(6, 0, [self.group.id])],
                "start_date": today,
                "cutoff_day": "1",  # Tuesday
                "pickup_day": "5",  # Saturday (AFTER Tuesday)
                "period": "weekly",
            }
        )
        self.assertTrue(order1.id, "Valid configuration should create order")

        # Valid: pickup == cutoff (same day)
        order2 = self.env["group.order"].create(
            {
                "name": "Valid Order 2",
                "group_ids": [(6, 0, [self.group.id])],
                "start_date": today,
                "cutoff_day": "5",  # Saturday
                "pickup_day": "5",  # Saturday (SAME DAY)
                "period": "weekly",
            }
        )
        self.assertTrue(order2.id, "Same day configuration should be allowed")

    def test_all_weekday_combinations_consistency(self):
        """Test that all valid weekday combinations produce consistent results.

        This regression test ensures the date calculation logic works correctly
        for all 49 combinations of start_date × pickup_day (7 × 7).
        """
        today = fields.Date.today()
        errors = []

        for start_offset in range(7):  # 7 possible start days
            start_date = today + timedelta(days=start_offset)

            for pickup_weekday in range(7):  # 7 possible pickup days
                order = self.env["group.order"].create(
                    {
                        "name": f"Test S{start_offset}P{pickup_weekday}",
                        "group_ids": [(6, 0, [self.group.id])],
                        "start_date": start_date,
                        "pickup_day": str(pickup_weekday),
                        "period": "weekly",
                    }
                )

                # Validate pickup_date is set
                if not order.pickup_date:
                    errors.append(
                        f"start_offset={start_offset}, pickup_day={pickup_weekday}: "
                        f"pickup_date is None"
                    )
                    continue

                # Validate pickup_date is in the future or today
                if order.pickup_date < start_date:
                    errors.append(
                        f"start_offset={start_offset}, pickup_day={pickup_weekday}: "
                        f"pickup_date {order.pickup_date} < start_date {start_date}"
                    )

                # Validate pickup_date weekday matches pickup_day
                if order.pickup_date.weekday() != pickup_weekday:
                    errors.append(
                        f"start_offset={start_offset}, pickup_day={pickup_weekday}: "
                        f"pickup_date weekday is {order.pickup_date.weekday()}, "
                        f"expected {pickup_weekday}"
                    )

        self.assertEqual(
            len(errors),
            0,
            f"Found {len(errors)} errors in weekday combinations:\n"
            + "\n".join(errors),
        )

    def test_cron_update_dates_executes(self):
        """Test that cron job method executes without errors.

        New feature in v18.0.1.3.1: Daily cron job to recalculate dates
        for active orders to keep them up-to-date as time passes.
        """
        today = fields.Date.today()

        # Create multiple orders in different states
        orders = []
        for i, state in enumerate(["draft", "open", "closed"]):
            order = self.env["group.order"].create(
                {
                    "name": f"Test Cron Order {state}",
                    "group_ids": [(6, 0, [self.group.id])],
                    "start_date": today,
                    "pickup_day": str((i + 1) % 7),
                    "cutoff_day": str(i % 7),
                    "period": "weekly",
                    "state": state,
                }
            )
            orders.append(order)

        # Execute cron method (should not raise errors)
        try:
            self.env["group.order"]._cron_update_dates()
        except Exception as e:
            self.fail(f"Cron method raised exception: {e}")

        # Verify dates are still valid for active orders
        active_orders = [o for o in orders if o.state in ["draft", "open"]]
        for order in active_orders:
            self.assertIsNotNone(
                order.pickup_date,
                f"Pickup date should be set for active order {order.name}",
            )

    def test_cron_update_dates_skips_orders_without_cutoff_date(self):
        """Cron should not crash if an active order lacks cutoff_date.

        A malformed or partially configured active group order should be logged
        and skipped for sale-order confirmation, but must not block recomputing
        the rest of active orders.
        """
        today = fields.Date.today()

        valid_order = self.env["group.order"].create(
            {
                "name": "Valid Cron Order",
                "group_ids": [(6, 0, [self.group.id])],
                "start_date": today,
                "pickup_day": "2",  # Wednesday
                "cutoff_day": "0",  # Monday
                "period": "weekly",
                "state": "open",
            }
        )
        invalid_order = self.env["group.order"].create(
            {
                "name": "Invalid Cron Order Without Cutoff",
                "group_ids": [(6, 0, [self.group.id])],
                "start_date": today,
                "pickup_day": "2",  # Wednesday
                "cutoff_day": False,
                "period": "weekly",
                "state": "open",
            }
        )

        self.assertFalse(
            invalid_order.cutoff_date,
            "Precondition failed: invalid order should not have cutoff_date",
        )

        self.env["group.order"]._cron_update_dates()

        valid_order.invalidate_recordset()
        invalid_order.invalidate_recordset()

        self.assertTrue(valid_order.cutoff_date)
        self.assertTrue(valid_order.pickup_date)
        self.assertFalse(invalid_order.cutoff_date)
