import pytest

import pandas as pd


@pytest.fixture(
    params=[
        (pd.Timedelta("0 days"), pd.Timedelta("1 day")),
        (pd.Timestamp("2018-01-01"), pd.Timedelta("1 day")),
        (0, 1),
    ],
    ids=lambda x: type(x[0]).__name__,
)
def start_shift(request):
    """
    Fixture for generating intervals of types from a start value and a shift
    value that can be added to start to generate an endpoint
    """
    return request.param


class TestOverlaps:
    def test_overlaps_self(self, start_shift, closed):
        start, shift = start_shift
        interval = pd.Interval(start, start + shift, closed)
        assert interval.overlaps(interval)

    def test_overlaps_nested(self, start_shift, closed, other_closed):
        start, shift = start_shift
        interval1 = pd.Interval(start, start + 3 * shift, other_closed)
        interval2 = pd.Interval(start + shift, start + 2 * shift, closed)

        # nested intervals should always overlap
        assert interval1.overlaps(interval2)

    def test_overlaps_disjoint(self, start_shift, closed, other_closed):
        start, shift = start_shift
        interval1 = pd.Interval(start, start + shift, other_closed)
        interval2 = pd.Interval(start + 2 * shift, start + 3 * shift, closed)

        # disjoint intervals should never overlap
        assert not interval1.overlaps(interval2)

    def test_overlaps_endpoint(self, start_shift, closed, other_closed):
        start, shift = start_shift
        interval1 = pd.Interval(start, start + shift, other_closed)
        interval2 = pd.Interval(start + shift, start + 2 * shift, closed)

        # overlap if shared endpoint is closed for both (overlap at a point)
        result = interval1.overlaps(interval2)
        expected = interval1.closed_right and interval2.closed_left
        assert result == expected

    @pytest.mark.parametrize("empty_closed", ["left", "right", "neither"])
    @pytest.mark.parametrize("offset", [0, 1, 2])
    def test_overlaps_empty(self, start_shift, other_closed, empty_closed, offset):
        # GH#26893 an empty interval contains no points, so it never overlaps,
        # whether it lies inside the other interval or on one of its endpoints
        start, shift = start_shift
        interval = pd.Interval(start, start + 2 * shift, other_closed)
        point = start + offset * shift
        empty = pd.Interval(point, point, empty_closed)

        assert not interval.overlaps(empty)
        assert not empty.overlaps(interval)
        assert not empty.overlaps(empty)

    def test_overlaps_point(self, start_shift, closed):
        # GH#26893 Interval(x, x, closed="both") is a point, not empty
        start, shift = start_shift
        interval = pd.Interval(start, start + 2 * shift, closed)
        point = pd.Interval(start + shift, start + shift, "both")

        assert interval.overlaps(point)
        assert point.overlaps(interval)
        assert point.overlaps(point)

    @pytest.mark.parametrize(
        "other",
        [10, True, "foo", pd.Timedelta("1 day"), pd.Timestamp("2018-01-01")],
        ids=lambda x: type(x).__name__,
    )
    def test_overlaps_invalid_type(self, other):
        interval = pd.Interval(0, 1)
        msg = f"`other` must be an Interval, got {type(other).__name__}"
        with pytest.raises(TypeError, match=msg):
            interval.overlaps(other)
