"""Unit tests for bucket date range utilities.

Tests verify:
1. ISO 8601 week boundary calculations
2. Month boundaries handle all month lengths correctly
3. Quarter boundaries map to correct months
4. Clamping to filter ranges works correctly
5. Edge cases: None labels, invalid formats, year boundaries
"""

from datetime import date

import pytest

from testio_mcp.utilities.bucket_utils import (
    BucketBoundaries,
    format_bucket_metadata,
    get_bucket_boundaries,
    get_month_boundaries,
    get_quarter_boundaries,
    get_week_boundaries,
)

pytestmark = pytest.mark.unit


class TestGetWeekBoundaries:
    """Test strftime %W week boundary calculations."""

    def test_week_48_2024(self) -> None:
        """Test week 48 of 2024.

        2024 starts on Monday, so week 00 = Dec 25-31 2023, week 01 = Jan 1-7.
        Week 48 = Nov 25 - Dec 1.
        """
        monday, sunday = get_week_boundaries("2024-W48")
        assert monday == date(2024, 11, 25)
        assert sunday == date(2024, 12, 1)

    def test_week_01_2024(self) -> None:
        """Test week 1 of 2024.

        2024 starts on Monday, so week 01 starts Jan 1.
        """
        monday, sunday = get_week_boundaries("2024-W01")
        assert monday == date(2024, 1, 1)
        assert sunday == date(2024, 1, 7)

    def test_week_00_2024(self) -> None:
        """Test week 0 of 2024.

        2024 starts on Monday, so week 00 is the week before (Dec 25-31 2023).
        """
        monday, sunday = get_week_boundaries("2024-W00")
        assert monday == date(2023, 12, 25)
        assert sunday == date(2023, 12, 31)

    def test_week_52_2024(self) -> None:
        """Test week 52 of 2024 (Dec 23 - Dec 29)."""
        monday, sunday = get_week_boundaries("2024-W52")
        assert monday == date(2024, 12, 23)
        assert sunday == date(2024, 12, 29)

    def test_week_00_2025(self) -> None:
        """Test week 0 of 2025.

        2025 starts on Wednesday, so week 00 Monday is Dec 30 2024.
        """
        monday, sunday = get_week_boundaries("2025-W00")
        assert monday == date(2024, 12, 30)
        assert sunday == date(2025, 1, 5)

    def test_invalid_format_raises(self) -> None:
        """Test invalid format raises ValueError."""
        with pytest.raises(ValueError, match="Invalid week label format"):
            get_week_boundaries("2024-48")  # Missing W

        with pytest.raises(ValueError, match="Invalid week label format"):
            get_week_boundaries("W48-2024")  # Wrong order

    def test_invalid_week_number_raises(self) -> None:
        """Test invalid week numbers raise ValueError."""
        with pytest.raises(ValueError, match="Invalid week number"):
            get_week_boundaries("2024-W54")  # Max is 53

        with pytest.raises(ValueError, match="Invalid week label format"):
            get_week_boundaries("2024-W-1")  # Negative (format won't match)

    def test_single_digit_week(self) -> None:
        """Test single digit week number works."""
        monday, sunday = get_week_boundaries("2024-W1")
        assert monday == date(2024, 1, 1)
        assert sunday == date(2024, 1, 7)


class TestGetMonthBoundaries:
    """Test month boundary calculations."""

    def test_november_2024(self) -> None:
        """Test November 2024 (30 days)."""
        first, last = get_month_boundaries("2024-11")
        assert first == date(2024, 11, 1)
        assert last == date(2024, 11, 30)

    def test_december_2024(self) -> None:
        """Test December 2024 (31 days)."""
        first, last = get_month_boundaries("2024-12")
        assert first == date(2024, 12, 1)
        assert last == date(2024, 12, 31)

    def test_february_leap_year(self) -> None:
        """Test February in leap year (29 days)."""
        first, last = get_month_boundaries("2024-02")
        assert first == date(2024, 2, 1)
        assert last == date(2024, 2, 29)

    def test_february_non_leap_year(self) -> None:
        """Test February in non-leap year (28 days)."""
        first, last = get_month_boundaries("2023-02")
        assert first == date(2023, 2, 1)
        assert last == date(2023, 2, 28)

    def test_invalid_format_raises(self) -> None:
        """Test invalid format raises ValueError."""
        with pytest.raises(ValueError, match="Invalid month label format"):
            get_month_boundaries("2024-1")  # Single digit month

        with pytest.raises(ValueError, match="Invalid month label format"):
            get_month_boundaries("11-2024")  # Wrong order

    def test_invalid_month_raises(self) -> None:
        """Test invalid month number raises ValueError."""
        with pytest.raises(ValueError, match="Invalid month"):
            get_month_boundaries("2024-13")

        with pytest.raises(ValueError, match="Invalid month"):
            get_month_boundaries("2024-00")


class TestGetQuarterBoundaries:
    """Test quarter boundary calculations."""

    def test_q1_2024(self) -> None:
        """Test Q1 2024 (Jan 1 - Mar 31)."""
        first, last = get_quarter_boundaries("2024-Q1")
        assert first == date(2024, 1, 1)
        assert last == date(2024, 3, 31)

    def test_q2_2024(self) -> None:
        """Test Q2 2024 (Apr 1 - Jun 30)."""
        first, last = get_quarter_boundaries("2024-Q2")
        assert first == date(2024, 4, 1)
        assert last == date(2024, 6, 30)

    def test_q3_2024(self) -> None:
        """Test Q3 2024 (Jul 1 - Sep 30)."""
        first, last = get_quarter_boundaries("2024-Q3")
        assert first == date(2024, 7, 1)
        assert last == date(2024, 9, 30)

    def test_q4_2024(self) -> None:
        """Test Q4 2024 (Oct 1 - Dec 31)."""
        first, last = get_quarter_boundaries("2024-Q4")
        assert first == date(2024, 10, 1)
        assert last == date(2024, 12, 31)

    def test_invalid_quarter_number_raises(self) -> None:
        """Test invalid quarter number raises ValueError."""
        with pytest.raises(ValueError, match="Invalid quarter label format"):
            get_quarter_boundaries("2024-Q5")

        with pytest.raises(ValueError, match="Invalid quarter label format"):
            get_quarter_boundaries("2024-Q0")

    def test_invalid_format_raises(self) -> None:
        """Test invalid format raises ValueError."""
        with pytest.raises(ValueError, match="Invalid quarter label format"):
            get_quarter_boundaries("Q4-2024")  # Wrong order

        with pytest.raises(ValueError, match="Invalid quarter label format"):
            get_quarter_boundaries("2024-4")  # Missing Q


class TestGetBucketBoundaries:
    """Test combined bucket boundary calculation with clamping."""

    def test_week_no_clamping(self) -> None:
        """Test week boundaries without filter clamping."""
        result = get_bucket_boundaries("week", "2024-W48")
        assert result is not None
        assert result.full_start == date(2024, 11, 25)
        assert result.full_end == date(2024, 12, 1)
        assert result.start == result.full_start
        assert result.end == result.full_end

    def test_week_clamped_start(self) -> None:
        """Test week boundaries with filter clamping start."""
        result = get_bucket_boundaries(
            "week",
            "2024-W48",
            filter_start=date(2024, 11, 30),  # Saturday
            filter_end=None,
        )
        assert result is not None
        assert result.start == date(2024, 11, 30)  # Clamped
        assert result.end == date(2024, 12, 1)  # Not clamped
        assert result.full_start == date(2024, 11, 25)  # Canonical
        assert result.full_end == date(2024, 12, 1)  # Canonical

    def test_week_clamped_end(self) -> None:
        """Test week boundaries with filter clamping end."""
        result = get_bucket_boundaries(
            "week",
            "2024-W48",
            filter_start=None,
            filter_end=date(2024, 11, 28),  # Thursday
        )
        assert result is not None
        assert result.start == date(2024, 11, 25)  # Not clamped
        assert result.end == date(2024, 11, 28)  # Clamped
        assert result.full_start == date(2024, 11, 25)
        assert result.full_end == date(2024, 12, 1)

    def test_week_clamped_both(self) -> None:
        """Test week boundaries with both start and end clamped."""
        result = get_bucket_boundaries(
            "week",
            "2024-W48",
            filter_start=date(2024, 11, 30),
            filter_end=date(2024, 11, 30),
        )
        assert result is not None
        assert result.start == date(2024, 11, 30)
        assert result.end == date(2024, 11, 30)
        assert result.full_start == date(2024, 11, 25)
        assert result.full_end == date(2024, 12, 1)

    def test_month_clamped(self) -> None:
        """Test month boundaries with clamping."""
        result = get_bucket_boundaries(
            "month",
            "2024-11",
            filter_start=date(2024, 11, 15),
            filter_end=date(2024, 11, 20),
        )
        assert result is not None
        assert result.start == date(2024, 11, 15)
        assert result.end == date(2024, 11, 20)
        assert result.full_start == date(2024, 11, 1)
        assert result.full_end == date(2024, 11, 30)

    def test_quarter_clamped(self) -> None:
        """Test quarter boundaries with clamping."""
        result = get_bucket_boundaries(
            "quarter",
            "2024-Q4",
            filter_start=date(2024, 11, 1),
            filter_end=date(2024, 11, 30),
        )
        assert result is not None
        assert result.start == date(2024, 11, 1)
        assert result.end == date(2024, 11, 30)
        assert result.full_start == date(2024, 10, 1)
        assert result.full_end == date(2024, 12, 31)

    def test_none_label_returns_none(self) -> None:
        """Test that None label returns None."""
        result = get_bucket_boundaries("week", None)
        assert result is None

    def test_invalid_dimension_raises(self) -> None:
        """Test that non-time dimension raises ValueError."""
        with pytest.raises(ValueError, match="Not a time dimension"):
            get_bucket_boundaries("feature", "Login")


class TestFormatBucketMetadata:
    """Test bucket metadata formatting."""

    def test_format_to_iso_strings(self) -> None:
        """Test formatting produces ISO date strings."""
        boundaries = BucketBoundaries(
            start=date(2024, 11, 30),
            end=date(2024, 12, 1),
            full_start=date(2024, 11, 25),
            full_end=date(2024, 12, 1),
        )
        result = format_bucket_metadata(boundaries)
        assert result == {
            "start": "2024-11-30",
            "end": "2024-12-01",
            "full_start": "2024-11-25",
            "full_end": "2024-12-01",
        }

    def test_single_day_bucket(self) -> None:
        """Test formatting single day bucket (start == end)."""
        boundaries = BucketBoundaries(
            start=date(2024, 11, 30),
            end=date(2024, 11, 30),
            full_start=date(2024, 11, 25),
            full_end=date(2024, 12, 1),
        )
        result = format_bucket_metadata(boundaries)
        assert result["start"] == result["end"] == "2024-11-30"
