from collections import (
    OrderedDict,
    namedtuple,
)

import pytest

from syrupy.extensions.amber.serializer import AmberDataSerializer


def test_non_snapshots(snapshot):
    with pytest.raises(AssertionError):
        assert "Lorem ipsum." == "Muspi merol."


def test_reflection(snapshot):
    """assert [expected|snapshot] == [actual|received]"""
    assert snapshot(name="reflectionA") == snapshot
    assert snapshot == snapshot
    assert snapshot == snapshot(name="reflectionA")


def test_empty_snapshot(snapshot):
    assert snapshot == None  # noqa: E711
    assert snapshot == ""


def test_snapshot_markers(snapshot):
    """
    Test snapshot markers do not break serialization when in snapshot data
    """
    marker_strings = (
        AmberDataSerializer._marker_prefix,
        f"{AmberDataSerializer._indent}{AmberDataSerializer._marker_prefix}",
        f"{AmberDataSerializer._marker_prefix}{AmberDataSerializer.Marker.Divider}",
        f"{AmberDataSerializer._marker_prefix}{AmberDataSerializer.Marker.Name}:",
    )
    assert snapshot == "\n".join(marker_strings)


def test_newline_control_characters(snapshot):
    assert snapshot == "line 1\nline 2"
    assert snapshot == "line 1\r\nline 2"
    assert snapshot == "line 1\r\nline 2\r\n"
    assert snapshot == "line 1\rline 2\r"
    assert snapshot == "line 1\rline 2\n"
    assert snapshot == "line 1\rline 2"


def test_multiline_string_in_dict(snapshot):
    lines = "\n".join(["line 1", "line 2"])
    assert {"value": lines} == snapshot


def test_deeply_nested_multiline_string_in_dict(snapshot):
    lines = "\n".join(["line 1", "line 2", "line 3"])
    d = {"value_a": {"value_b": lines}}
    assert d == snapshot


@pytest.mark.parametrize("actual", [False, True])
def test_bool(actual, snapshot):
    assert actual == snapshot


@pytest.mark.parametrize(
    "actual",
    [
        "",
        r"Raw string",
        r"Escaped \n",
        r"Backslash \u U",
        "🥞🐍🍯",
        "singleline:",
        "- singleline",
        "multi-line\nline 2\nline 3",
        "multi-line\nline 2\n  line 3",
        "string with 'quotes'",
        b"Byte string",
    ],
    ids=lambda x: "",
)
def test_string(snapshot, actual):
    assert snapshot == actual


def test_multiple_snapshots(snapshot):
    assert "First." == snapshot
    snapshot.assert_match("Second.")
    assert snapshot == "Third."


ExampleTuple = namedtuple("ExampleTuple", ["a", "b", "c", "d"])


def test_tuple(snapshot):
    assert snapshot == ("this", "is", ("a", "tuple"))
    assert snapshot == ExampleTuple(a="this", b="is", c="a", d={"named", "tuple"})
    assert snapshot == ()


@pytest.mark.parametrize(
    "actual",
    [
        {"this", "is", "a", "set"},
        {"contains", "frozen", frozenset({"1", "2"})},
        {"contains", "tuple", (1, 2)},
        {"contains", "namedtuple", ExampleTuple(a=1, b=2, c=3, d=4)},
        set(),
    ],
)
def test_set(snapshot, actual):
    assert snapshot == actual


@pytest.mark.parametrize(
    "actual",
    [
        {"b": True, "c": "Some text.", "d": ["1", 2], "a": {"e": False}},
        {"b": True, "c": "Some ttext.", "d": ["1", 2], "a": {"e": False}},
        {
            1: True,
            "a": "Some ttext.",
            "multi\nline\nkey": "Some morre text.",
            frozenset({"1", "2"}): ["1", 2],
            ExampleTuple(a=1, b=2, c=3, d=4): {"e": False},
        },
        {},
        {"key": ["line1\nline2"]},
        {"key": [1, "line1\nline2", 2, "line3\nline4"]},
        {"key": [1, ["line1\nline2"], 2]},
    ],
)
def test_dict(snapshot, actual):
    assert actual == snapshot


def test_numbers(snapshot):
    assert snapshot == 3.5
    assert snapshot == 7
    assert snapshot == 2 / 6


@pytest.mark.parametrize(
    "actual",
    [
        [],
        ["this", "is", "a", "list"],
        ["contains", "empty", []],
        [1, 2, "string", {"key": "value"}],
    ],
)
def test_list(snapshot, actual):
    assert actual == snapshot


list_cycle = [1, 2, 3]
list_cycle.append(list_cycle)

dict_cycle = {"a": 1, "b": 2, "c": 3}
dict_cycle.update(d=dict_cycle)


@pytest.mark.parametrize("cyclic", [list_cycle, dict_cycle])
def test_cycle(cyclic, snapshot):
    assert cyclic == snapshot


class CustomClass:
    a = 1
    b = "2"
    c = list_cycle
    d = dict_cycle
    _protected_variable = None
    __private_variable = None

    def __init__(self, x=None):
        self.x = x
        self._y = 1
        self.__z = 2

    def public_method(self, a, b=1, *, c, d=None):
        pass

    def _protected_method(self):
        pass

    def __private_method(self):
        pass


def test_custom_object_repr(snapshot):
    assert CustomClass(CustomClass()) == snapshot


class TestClass:
    def test_class_method_name(self, snapshot):
        assert snapshot == "this is in a test class"

    @pytest.mark.parametrize("actual", ["a", "b", "c"])
    def test_class_method_parametrized(self, snapshot, actual):
        assert snapshot == actual

    @pytest.mark.parametrize("actual", ["x", "y", "z"])
    class TestNestedClass:
        def test_nested_class_method(self, snapshot, actual):
            assert snapshot == f"parameterized nested class method {actual}"


class TestSubClass(TestClass):
    pass


@pytest.mark.parametrize("parameter_with_dot", ("value.with.dot",))
def test_parameter_with_dot(parameter_with_dot, snapshot):
    assert parameter_with_dot == snapshot


@pytest.mark.parametrize("parameter_1", ("foo",))
@pytest.mark.parametrize("parameter_2", ("bar",))
def test_doubly_parametrized(parameter_1, parameter_2, snapshot):
    assert parameter_1 == snapshot
    assert parameter_2 == snapshot


def test_ordered_dict(snapshot):
    d = OrderedDict()
    d["b"] = 0
    d["a"] = 1
    assert snapshot == d


def test_many_sorted(snapshot):
    for i in range(25):
        assert i == snapshot
