from __future__ import annotations

from typing import TYPE_CHECKING, Any

import pytest

import narwhals as nw
from narwhals._utils import Implementation, Version
from tests.utils import POLARS_VERSION

if TYPE_CHECKING:
    from tests.utils import ConstructorEager


class MockCompliantSeries:
    _version = Version.MAIN
    _implementation = Implementation.UNKNOWN

    def __narwhals_series__(self) -> Any:
        return self

    @property
    def native(self) -> tuple[()]:
        return ()

    @property
    def dtype(self) -> nw.Categorical:
        return nw.Categorical()


def test_is_ordered_categorical_polars() -> None:
    pytest.importorskip("polars")
    import polars as pl

    s = pl.Series(["a", "b"], dtype=pl.Categorical)
    if POLARS_VERSION < (1, 32):  # pragma: no cover
        assert nw.is_ordered_categorical(nw.from_native(s, series_only=True))
    else:
        # Post 1.32 there's no physical ordering for categoricals anymore.
        assert not nw.is_ordered_categorical(nw.from_native(s, series_only=True))

    cat_dtype = (
        pl.Categorical(ordering="lexical")
        if POLARS_VERSION < (1, 32)
        else pl.Categorical()
    )
    s = pl.Series(["a", "b"], dtype=cat_dtype)
    assert not nw.is_ordered_categorical(nw.from_native(s, series_only=True))
    s = pl.Series(["a", "b"], dtype=pl.Enum(["a", "b"]))
    assert nw.is_ordered_categorical(nw.from_native(s, series_only=True))


def test_is_ordered_categorical_pandas() -> None:
    pytest.importorskip("pandas")
    import pandas as pd

    s = pd.Series(["a", "b"], dtype=pd.CategoricalDtype(ordered=True))
    assert nw.is_ordered_categorical(nw.from_native(s, series_only=True))
    s = pd.Series(["a", "b"], dtype=pd.CategoricalDtype(ordered=False))
    assert not nw.is_ordered_categorical(nw.from_native(s, series_only=True))


def test_is_ordered_categorical_pyarrow_string() -> None:
    pytest.importorskip("pyarrow")
    import pyarrow as pa

    tp = pa.dictionary(pa.int32(), pa.string())
    s = pa.chunked_array([pa.array(["a", "b"], type=tp)], type=tp)
    assert not nw.is_ordered_categorical(nw.from_native(s, series_only=True))


def test_is_definitely_not_ordered_categorical(
    constructor_eager: ConstructorEager,
) -> None:
    assert not nw.is_ordered_categorical(
        nw.from_native(constructor_eager({"a": [1, 2, 3]}), eager_only=True)["a"]
    )


@pytest.mark.xfail(reason="https://github.com/apache/arrow/issues/41017")
def test_is_ordered_categorical_pyarrow() -> None:
    pytest.importorskip("pyarrow")
    import pyarrow as pa

    tp = pa.dictionary(pa.int32(), pa.string(), ordered=True)
    arr = pa.array(["a", "b"], type=tp)
    s = pa.chunked_array([arr], type=tp)
    assert nw.is_ordered_categorical(
        nw.from_native(s, series_only=True)
    )  # pragma: no cover


def test_is_ordered_categorical_unknown_series() -> None:
    series: nw.Series[Any] = nw.Series(MockCompliantSeries(), level="full")
    assert nw.is_ordered_categorical(series) is False
