"""Integration tests for slice_objects covering sorting, integer, delta, calendar,
and mixed-bound slices."""

from datetime import datetime, timedelta, timezone
from types import SimpleNamespace

import pytest

from tals import slice_objects


def obj(dt: datetime) -> SimpleNamespace:
    return SimpleNamespace(start=dt)


def d(year, month, day, hour=0, tz=None):
    return datetime(year, month, day, hour, tzinfo=tz)


REF = datetime(2026, 3, 10)


# --- Sorting ---


def test_input_order_does_not_matter():
    a, b, c = obj(d(2026, 1, 1)), obj(d(2026, 2, 1)), obj(d(2026, 3, 1))
    assert slice_objects([c, a, b], ":", "start", REF) == [a, b, c]


def test_stable_sort_identical_timestamps():
    t = d(2026, 1, 1)
    x = SimpleNamespace(start=t, name="x")
    y = SimpleNamespace(start=t, name="y")
    result = slice_objects([x, y], ":", "start", REF)
    assert result == [x, y]  # original relative order preserved


# --- Single index ---


def test_index_first():
    a, b = obj(d(2026, 1, 1)), obj(d(2026, 2, 1))
    assert slice_objects([b, a], "0", "start", REF) is a


def test_index_last():
    a, b = obj(d(2026, 1, 1)), obj(d(2026, 2, 1))
    assert slice_objects([a, b], "-1", "start", REF) is b


def test_index_second_to_last():
    a, b, c = obj(d(2026, 1, 1)), obj(d(2026, 2, 1)), obj(d(2026, 3, 1))
    assert slice_objects([a, b, c], "-2", "start", REF) is b


# --- Integer slices ---


def test_slice_all():
    a, b, c = obj(d(2026, 1, 1)), obj(d(2026, 2, 1)), obj(d(2026, 3, 1))
    assert slice_objects([a, b, c], ":", "start", REF) == [a, b, c]


def test_slice_first_three():
    objs = [obj(d(2026, i, 1)) for i in range(1, 6)]
    assert slice_objects(objs, "0:3", "start", REF) == objs[:3]


def test_slice_all_but_last():
    a, b, c = obj(d(2026, 1, 1)), obj(d(2026, 2, 1)), obj(d(2026, 3, 1))
    assert slice_objects([a, b, c], ":-1", "start", REF) == [a, b]


def test_slice_last_three():
    objs = [obj(d(2026, i, 1)) for i in range(1, 6)]
    assert slice_objects(objs, "-3:", "start", REF) == objs[-3:]


def test_slice_all_but_first_and_last():
    a, b, c = obj(d(2026, 1, 1)), obj(d(2026, 2, 1)), obj(d(2026, 3, 1))
    assert slice_objects([a, b, c], "1:-1", "start", REF) == [b]


# --- Time-delta slices ---


def test_delta_open_end():
    # -7d from 2026-03-10 = 2026-03-03; only objects on/after that date
    a = obj(d(2026, 3, 2))  # before threshold
    b = obj(d(2026, 3, 3))  # exactly at threshold — included (start is inclusive)
    c = obj(d(2026, 3, 5))
    assert slice_objects([a, b, c], "-7d:", "start", REF) == [b, c]


def test_delta_open_start():
    # [:-7d] means timestamp < 2026-03-03
    a = obj(d(2026, 3, 2))  # included (< threshold)
    b = obj(d(2026, 3, 3))  # excluded (= threshold, end is exclusive)
    c = obj(d(2026, 3, 5))
    assert slice_objects([a, b, c], ":-7d", "start", REF) == [a]


def test_delta_both_sides():
    # -7d = Mar 3, -1d = Mar 9; [Mar 3, Mar 9)
    a = obj(d(2026, 3, 3))  # included
    b = obj(d(2026, 3, 8))  # included
    c = obj(d(2026, 3, 9))  # excluded (end exclusive)
    d_ = obj(d(2026, 3, 2))  # excluded (before start)
    assert slice_objects([a, b, c, d_], "-7d:-1d", "start", REF) == [a, b]


# --- Calendar slices ---


def test_calendar_this_month():
    a = obj(d(2026, 2, 28))  # before March
    b = obj(d(2026, 3, 1))  # start of March — inclusive
    c = obj(d(2026, 3, 10))
    assert slice_objects([a, b, c], "0M:", "start", REF) == [b, c]


def test_calendar_last_month_to_this():
    # [-1M:0M] = [Feb 1, Mar 1)
    a = obj(d(2026, 1, 31))  # excluded
    b = obj(d(2026, 2, 1))  # included (start inclusive)
    c = obj(d(2026, 2, 28))  # included
    e = obj(d(2026, 3, 1))  # excluded (end exclusive)
    assert slice_objects([a, b, c, e], "-1M:0M", "start", REF) == [b, c]


def test_calendar_this_year():
    a = obj(d(2025, 12, 31))  # excluded
    b = obj(d(2026, 1, 1))  # included
    c = obj(d(2026, 3, 10))
    assert slice_objects([a, b, c], "0Y:", "start", REF) == [b, c]


def test_calendar_this_week():
    # REF = 2026-03-10 (Tuesday), week_start=0 → week starts Mon Mar 9
    a = obj(d(2026, 3, 8))  # Sunday before — excluded
    b = obj(d(2026, 3, 9))  # Monday — included
    c = obj(d(2026, 3, 10))  # Tuesday — included
    assert slice_objects([a, b, c], "0W:", "start", REF, week_start=0) == [b, c]


def test_calendar_next_month_exclusive_end():
    # [0M:+1M] = [Mar 1, Apr 1)
    a = obj(d(2026, 2, 28))
    b = obj(d(2026, 3, 1))
    c = obj(d(2026, 3, 31))
    e = obj(d(2026, 4, 1))  # excluded
    assert slice_objects([a, b, c, e], "0M:+1M", "start", REF) == [b, c]


# --- Mixed bounds ---


def test_mixed_delta_int():
    # [-7d:-1]: filter >= Mar 3, then drop last
    a = obj(d(2026, 3, 3))
    b = obj(d(2026, 3, 7))
    c = obj(d(2026, 3, 10))
    # filtered = [a, b, c], [:-1] = [a, b]
    assert slice_objects([a, b, c], "-7d:-1", "start", REF) == [a, b]


def test_mixed_calendar_int():
    # [0M:-1]: filter >= Mar 1, then drop last
    a = obj(d(2026, 2, 15))
    b = obj(d(2026, 3, 1))
    c = obj(d(2026, 3, 10))
    # filtered = [b, c], [:-1] = [b]
    assert slice_objects([a, b, c], "0M:-1", "start", REF) == [b]
