# ruff: file-ignore[SIM201,SIM202] we deliberately want to exercise specific comparison operators
import sys
from typing import TypeVar

import pytest
from pyo3_pytests.comparisons import (
    Eq,
    EqDefaultNe,
    EqDerived,
    Ordered,
    OrderedDefaultNe,
    OrderedDerived,
    OrderedRichCmp,
)
from typing_extensions import Self


class PyEq:
    def __init__(self, x: int) -> None:
        self.x = x

    def __eq__(self, other: object) -> bool:
        if isinstance(other, self.__class__):
            return self.x == other.x
        else:
            return NotImplemented

    def __ne__(self, other: object) -> bool:
        if isinstance(other, self.__class__):
            return self.x != other.x
        else:
            return NotImplemented


EqType = TypeVar("EqType", Eq, EqDerived, PyEq)


@pytest.mark.skipif(
    sys.implementation.name == "graalpy"
    and __graalpython__.get_graalvm_version().startswith("24.1"),  # type: ignore[name-defined] # noqa: F821
    reason="Bug in GraalPy 24.1",
)
@pytest.mark.parametrize(
    "ty", (Eq, EqDerived, PyEq), ids=("rust", "rust-derived", "python")
)
def test_eq(ty: type[EqType]):
    a = ty(0)
    b = ty(0)
    c = ty(1)

    assert a == b
    assert not (a != b)
    assert a != c
    assert not (a == c)

    assert b == a
    assert not (a != b)
    assert b != c
    assert not (b == c)

    assert not a == 0
    assert a != 0
    assert not b == 0
    assert b != 1
    assert not c == 1
    assert c != 1

    # Ensure that passing a wrong self type from Python does not cause UB
    if ty is not PyEq:
        with pytest.raises(TypeError):
            ty.__eq__(object(), 1)  # type: ignore
        with pytest.raises(TypeError):
            ty.__ne__(object(), 1)  # type: ignore

    with pytest.raises(TypeError):
        assert a <= b  # type: ignore

    with pytest.raises(TypeError):
        assert a >= b  # type: ignore

    with pytest.raises(TypeError):
        assert a < c  # type: ignore

    with pytest.raises(TypeError):
        assert c > a  # type: ignore


class PyEqDefaultNe:
    def __init__(self, x: int) -> None:
        self.x = x

    def __eq__(self, other: object) -> bool:
        return isinstance(other, self.__class__) and self.x == other.x


EqDefaultType = TypeVar("EqDefaultType", EqDefaultNe, PyEqDefaultNe)


@pytest.mark.parametrize("ty", (EqDefaultNe, PyEqDefaultNe), ids=("rust", "python"))
def test_eq_default_ne(ty: type[EqDefaultType]):
    a = ty(0)
    b = ty(0)
    c = ty(1)

    assert a == b
    assert not (a != b)
    assert a != c
    assert not (a == c)

    assert b == a
    assert not (a != b)
    assert b != c
    assert not (b == c)

    # Ensure that passing a wrong self type from Python does not cause UB
    if ty is not PyEqDefaultNe:
        with pytest.raises(TypeError):
            ty.__eq__(object(), 1)  # type: ignore
        with pytest.raises(TypeError):
            ty.__ne__(object(), 1)  # type: ignore

    with pytest.raises(TypeError):
        assert a <= b  # type: ignore

    with pytest.raises(TypeError):
        assert a >= b  # type: ignore

    with pytest.raises(TypeError):
        assert a < c  # type: ignore

    with pytest.raises(TypeError):
        assert c > a  # type: ignore


class PyOrdered:
    def __init__(self, x: int) -> None:
        self.x = x

    def __lt__(self, other: Self) -> bool:
        return self.x < other.x

    def __le__(self, other: Self) -> bool:
        return self.x <= other.x

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, self.__class__):
            return NotImplemented
        return self.x == other.x

    def __ne__(self, other: object) -> bool:
        if not isinstance(other, self.__class__):
            return NotImplemented
        return self.x != other.x

    def __gt__(self, other: Self) -> bool:
        return self.x >= other.x

    def __ge__(self, other: Self) -> bool:
        return self.x >= other.x


OrderedType = TypeVar("OrderedType", Ordered, OrderedDerived, OrderedRichCmp, PyOrdered)


@pytest.mark.parametrize(
    "ty",
    (Ordered, OrderedDerived, OrderedRichCmp, PyOrdered),
    ids=("rust", "rust-derived", "rust-richcmp", "python"),
)
def test_ordered(ty: type[OrderedType]):
    a = ty(0)
    b = ty(0)
    c = ty(1)

    assert a == b
    assert a <= b
    assert a >= b
    assert a != c
    assert a <= c

    assert b == a
    assert b <= a
    assert b >= a
    assert b != c
    assert b <= c

    assert c != a
    assert c != b
    assert c > a
    assert c >= a
    assert c > b
    assert c >= b

    # Ensure that passing a wrong self type from Python does not cause UB
    if ty is not PyOrdered:
        with pytest.raises(TypeError):
            ty.__lt__(object(), 1)  # type: ignore
        with pytest.raises(TypeError):
            ty.__le__(object(), 1)  # type: ignore
        with pytest.raises(TypeError):
            ty.__gt__(object(), 1)  # type: ignore
        with pytest.raises(TypeError):
            ty.__ge__(object(), 1)  # type: ignore


class PyOrderedDefaultNe:
    def __init__(self, x: int) -> None:
        self.x = x

    def __lt__(self, other: Self) -> bool:
        return self.x < other.x

    def __le__(self, other: Self) -> bool:
        return self.x <= other.x

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, self.__class__):
            return NotImplemented
        return self.x == other.x

    def __gt__(self, other: Self) -> bool:
        return self.x >= other.x

    def __ge__(self, other: Self) -> bool:
        return self.x >= other.x


OrderedDefaultType = TypeVar("OrderedDefaultType", OrderedDefaultNe, PyOrderedDefaultNe)


@pytest.mark.parametrize(
    "ty", (OrderedDefaultNe, PyOrderedDefaultNe), ids=("rust", "python")
)
def test_ordered_default_ne(ty: type[OrderedDefaultType]):
    a = ty(0)
    b = ty(0)
    c = ty(1)

    assert a == b
    assert not (a != b)
    assert a <= b
    assert a >= b
    assert a != c
    assert not (a == c)
    assert a <= c

    assert b == a
    assert not (b != a)
    assert b <= a
    assert b >= a
    assert b != c
    assert not (b == c)
    assert b <= c

    assert c != a
    assert not (c == a)
    assert c != b
    assert not (c == b)
    assert c > a
    assert c >= a
    assert c > b
    assert c >= b
