# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
# SPDX-License-Identifier: BSD-2-Clause
"""Make sure the `as_dict_rec()` function behaves."""

from __future__ import annotations

import dataclasses
import functools
import ipaddress
import operator
import pathlib
import typing

import pytest

import as_dict_rec


if typing.TYPE_CHECKING:
    from collections.abc import Callable
    from typing import Any, Final


@dataclasses.dataclass(frozen=True)
class Nothing:
    """Nothing to say about it."""


@functools.total_ordering
class WeirdButDict:
    """A weird class that can be handled via its `__dict__` method."""

    name: str
    """The name of the thing."""

    def __init__(self, name: str) -> None:
        """Construct a [`WeirdButDict`] object with the specified name."""
        self.name = name

    def __eq__(self, other: object) -> bool:
        """Compare some weirdly comparable things."""
        assert isinstance(other, type(self))
        return self.name == other.name

    def __lt__(self, other: object) -> bool:
        """Compare some weirdly comparable things."""
        assert isinstance(other, type(self))
        return self.name < other.name

    def __hash__(self) -> int:
        """Return some kind of identifier."""
        return hash(self.name)


@dataclasses.dataclass(frozen=True)
class Something:
    """Something to say about it."""

    nothing: Nothing

    things: list[int | str | ipaddress.IPv4Address | None]

    random_things: set[pathlib.Path]

    class_dict: list[WeirdButDict]

    class_dict_set: set[WeirdButDict]


COMPLEX_THING: Final = Something(
    nothing=Nothing(),
    things=[0, None, 1, "42", ipaddress.IPv4Address("6.16.6.16"), None],
    random_things={pathlib.Path("/var"), pathlib.Path("/etc")},
    class_dict=[WeirdButDict("hello"), WeirdButDict("goodbye")],
    class_dict_set={WeirdButDict("hello"), WeirdButDict("goodbye")},
)

COMPLEX_RESULT: Final = {
    "nothing": {},
    "things": [0, None, 1, "42", "6.16.6.16", None],
    "random_things": ["/etc", "/var"],
    "class_dict": [{"name": "hello"}, {"name": "goodbye"}],
    "class_dict_set": [{"name": "goodbye"}, {"name": "hello"}],
}

COMPLEX_RESULT_NON_NULL: Final = {
    "nothing": {},
    "things": [0, 1, "42", "6.16.6.16"],
    "random_things": ["/etc", "/var"],
    "class_dict": [{"name": "hello"}, {"name": "goodbye"}],
    "class_dict_set": [{"name": "goodbye"}, {"name": "hello"}],
}


@pytest.mark.parametrize(
    "value",
    [
        None,
        0,
        1,
        42,
        -616,
        0.0,
        1.5,
        42.616,
        "",
        "None!",
        "0!",
        "1!",
        "Nothing to see here",
    ],
)
def test_basic_identical(value: Any) -> None:  # noqa: ANN401  # it could really be anything
    """Make sure the basic types are preserved."""
    res: Final = as_dict_rec.as_dict_rec(value)
    assert res is value


def test_complicated() -> None:
    """Make sure a complex structure remains the same."""
    assert as_dict_rec.as_dict_rec(COMPLEX_THING) == COMPLEX_RESULT
    assert as_dict_rec.as_dict_rec(COMPLEX_THING, skip_null=True) == COMPLEX_RESULT_NON_NULL


def test_override() -> None:
    """Make sure the serialization function may be overridden."""
    more_as_dict_handlers: Final[dict[type[Any], Callable[[Any], Any]]] = {
        WeirdButDict: lambda weird: "".join(reversed(weird.name)),
        int: operator.neg,
    }

    assert as_dict_rec.as_dict_rec(
        [1, "hello", WeirdButDict("hello")],
        overrides=as_dict_rec.Overrides(
            as_dict_handlers=as_dict_rec.AS_DICT_HANDLERS | more_as_dict_handlers,
        ),
    ) == [-1, "hello", "olleh"]
