from datetime import datetime, timedelta, timezone, timezone as tz

import pytest

from tals._parser import CalendarBound, DeltaBound
from tals._resolver import resolve_to_datetime

REF = datetime(2026, 3, 10, 12, 0)  # noon on a Tuesday
REF_AWARE = datetime(2026, 3, 10, 12, 0, tzinfo=timezone.utc)


# --- DeltaBound ---


def test_delta_days_negative():
    b = DeltaBound(-1, 7, "d")
    assert resolve_to_datetime(b, REF, 0) == REF - timedelta(days=7)


def test_delta_days_positive():
    b = DeltaBound(+1, 3, "d")
    assert resolve_to_datetime(b, REF, 0) == REF + timedelta(days=3)


def test_delta_hours():
    b = DeltaBound(-1, 2, "h")
    assert resolve_to_datetime(b, REF, 0) == REF - timedelta(hours=2)


def test_delta_minutes():
    b = DeltaBound(+1, 30, "m")
    assert resolve_to_datetime(b, REF, 0) == REF + timedelta(minutes=30)


def test_delta_preserves_aware_tz():
    b = DeltaBound(-1, 1, "d")
    result = resolve_to_datetime(b, REF_AWARE, 0)
    assert result.tzinfo == timezone.utc


# --- CalendarBound: Month ---


def test_month_current():
    b = CalendarBound(0, "M")
    assert resolve_to_datetime(b, REF, 0) == datetime(2026, 3, 1, 0, 0)


def test_month_previous():
    b = CalendarBound(-1, "M")
    assert resolve_to_datetime(b, REF, 0) == datetime(2026, 2, 1, 0, 0)


def test_month_two_back():
    b = CalendarBound(-2, "M")
    assert resolve_to_datetime(b, REF, 0) == datetime(2026, 1, 1, 0, 0)


def test_month_next():
    b = CalendarBound(1, "M")
    assert resolve_to_datetime(b, REF, 0) == datetime(2026, 4, 1, 0, 0)


def test_month_year_rollback():
    # -2M from February → December of previous year
    ref = datetime(2026, 2, 15)
    b = CalendarBound(-2, "M")
    assert resolve_to_datetime(b, ref, 0) == datetime(2025, 12, 1, 0, 0)


def test_month_preserves_aware_tz():
    b = CalendarBound(0, "M")
    result = resolve_to_datetime(b, REF_AWARE, 0)
    assert result == datetime(2026, 3, 1, 0, 0, tzinfo=timezone.utc)
    assert result.tzinfo == timezone.utc


# --- CalendarBound: Week ---


def test_week_current_monday_start():
    # REF = 2026-03-10, which is a Tuesday; week_start=0 (Monday)
    # Monday of that week = 2026-03-09
    b = CalendarBound(0, "W")
    assert resolve_to_datetime(b, REF, 0) == datetime(2026, 3, 9, 0, 0)


def test_week_current_sunday_start():
    # REF = 2026-03-10 (Tuesday); week_start=6 (Sunday)
    # Sunday of that week = 2026-03-08
    b = CalendarBound(0, "W")
    assert resolve_to_datetime(b, REF, 6) == datetime(2026, 3, 8, 0, 0)


def test_week_previous():
    b = CalendarBound(-1, "W")
    assert resolve_to_datetime(b, REF, 0) == datetime(2026, 3, 2, 0, 0)


def test_week_current_on_start_day():
    # reference_dt is Monday itself (week_start=0)
    ref = datetime(2026, 3, 9)  # Monday
    b = CalendarBound(0, "W")
    assert resolve_to_datetime(b, ref, 0) == datetime(2026, 3, 9, 0, 0)


# --- CalendarBound: Year ---


def test_year_current():
    b = CalendarBound(0, "Y")
    assert resolve_to_datetime(b, REF, 0) == datetime(2026, 1, 1, 0, 0)


def test_year_previous():
    b = CalendarBound(-1, "Y")
    assert resolve_to_datetime(b, REF, 0) == datetime(2025, 1, 1, 0, 0)


def test_year_next():
    b = CalendarBound(1, "Y")
    assert resolve_to_datetime(b, REF, 0) == datetime(2027, 1, 1, 0, 0)
