import pytest
from tals._parser import (
    CalendarBound,
    DeltaBound,
    IndexPosition,
    IntBound,
    SlicePosition,
    parse_position,
)


# --- Single index ---


def test_index_positive():
    assert parse_position("0") == IndexPosition(IntBound(0))


def test_index_negative():
    assert parse_position("-1") == IndexPosition(IntBound(-1))


def test_index_large():
    assert parse_position("42") == IndexPosition(IntBound(42))


def test_index_with_brackets():
    assert parse_position("[-1]") == IndexPosition(IntBound(-1))


def test_index_calendar_raises():
    with pytest.raises(ValueError, match="not valid as a single index"):
        parse_position("0M")


def test_index_delta_raises():
    with pytest.raises(ValueError, match="not valid as a single index"):
        parse_position("-7d")


# --- Open slices ---


def test_slice_open_both():
    assert parse_position(":") == SlicePosition(None, None)


def test_slice_open_start():
    assert parse_position(":-1") == SlicePosition(None, IntBound(-1))


def test_slice_open_end():
    assert parse_position("-1:") == SlicePosition(IntBound(-1), None)


# --- Integer slices ---


def test_slice_int_int():
    assert parse_position("0:3") == SlicePosition(IntBound(0), IntBound(3))


def test_slice_neg_int():
    assert parse_position("-3:") == SlicePosition(IntBound(-3), None)


# --- Delta bounds ---


@pytest.mark.parametrize(
    "s,expected",
    [
        ("-7d", DeltaBound(-1, 7, "d")),
        ("+2h", DeltaBound(+1, 2, "h")),
        ("-30m", DeltaBound(-1, 30, "m")),
        ("+1d", DeltaBound(+1, 1, "d")),
    ],
)
def test_delta_bound(s, expected):
    result = parse_position(f"{s}:")
    assert result.start == expected


def test_delta_slice():
    assert parse_position("-7d:-1d") == SlicePosition(
        DeltaBound(-1, 7, "d"), DeltaBound(-1, 1, "d")
    )


# --- Calendar bounds ---


@pytest.mark.parametrize(
    "s,expected",
    [
        ("0M", CalendarBound(0, "M")),
        ("-1M", CalendarBound(-1, "M")),
        ("-2M", CalendarBound(-2, "M")),
        ("+1M", CalendarBound(1, "M")),
        ("0W", CalendarBound(0, "W")),
        ("-1W", CalendarBound(-1, "W")),
        ("0Y", CalendarBound(0, "Y")),
        ("-1Y", CalendarBound(-1, "Y")),
        ("+1Y", CalendarBound(1, "Y")),
    ],
)
def test_calendar_bound(s, expected):
    result = parse_position(f"{s}:")
    assert result.start == expected


def test_calendar_slice():
    assert parse_position("-1M:0M") == SlicePosition(
        CalendarBound(-1, "M"), CalendarBound(0, "M")
    )


# --- Mixed bounds ---


def test_mixed_delta_int():
    result = parse_position("-7d:-1")
    assert result == SlicePosition(DeltaBound(-1, 7, "d"), IntBound(-1))


def test_mixed_calendar_int():
    result = parse_position("0M:-1")
    assert result == SlicePosition(CalendarBound(0, "M"), IntBound(-1))


# --- Errors ---


def test_too_many_colons():
    with pytest.raises(ValueError):
        parse_position("1:2:3")


def test_invalid_bound():
    with pytest.raises(ValueError):
        parse_position("abc")


def test_invalid_unit():
    with pytest.raises(ValueError):
        parse_position("-7x:")
