import dataclasses
from collections import Counter
from collections import OrderedDict
from collections import UserDict
from collections import UserList
from collections import defaultdict
from collections import namedtuple
from dataclasses import dataclass
from typing import NamedTuple

import pytest

from inline_snapshot import HasRepr
from inline_snapshot import snapshot
from inline_snapshot._code_repr import code_repr
from inline_snapshot._customize._custom_code import CustomCode
from inline_snapshot._customize._custom_sequence import CustomSet
from inline_snapshot._new_adapter import reeval
from inline_snapshot._sentinels import undefined
from inline_snapshot.extra import raises
from inline_snapshot.testing import Example
from tests.conftest import check_update


def test_enum():

    Example(
        {
            "color.py": """
from enum import Enum

class color(Enum):
    val="val"

def get_color():
    return [color.val,color.val]

             """,
            "test_color.py": """\
from inline_snapshot import snapshot
from color import get_color

def test_enum():
    assert get_color() == snapshot()
""",
        }
    ).run_inline(
        ["--inline-snapshot=create"],
        changed_files=snapshot({"test_color.py": """\
from inline_snapshot import snapshot
from color import get_color

from color import color

def test_enum():
    assert get_color() == snapshot([color.val, color.val])
"""}),
    )


def test_path():

    Example("""\
from pathlib import Path,PurePath
from inline_snapshot import snapshot

folder="a"

def test_a():
    assert Path(folder,"b.txt") == snapshot()
    assert PurePath(folder,"b.txt") == snapshot()
""").run_inline(
        ["--inline-snapshot=create"],
        changed_files=snapshot({"tests/test_something.py": """\
from pathlib import Path,PurePath
from inline_snapshot import snapshot

folder="a"

def test_a():
    assert Path(folder,"b.txt") == snapshot(Path("a/b.txt"))
    assert PurePath(folder,"b.txt") == snapshot(PurePath("a/b.txt"))
"""}),
    ).replace('"a"', '"c"').run_inline(
        ["--inline-snapshot=fix"],
        changed_files=snapshot({"tests/test_something.py": """\
from pathlib import Path,PurePath
from inline_snapshot import snapshot

folder="c"

def test_a():
    assert Path(folder,"b.txt") == snapshot(Path("c/b.txt"))
    assert PurePath(folder,"b.txt") == snapshot(PurePath("c/b.txt"))
"""}),
    )


def test_snapshot_generates_hasrepr():

    Example("""\
from inline_snapshot import snapshot

class Thing:
    def __repr__(self):
        return "<something>"

    def __eq__(self,other):
        if not isinstance(other,Thing):
            return NotImplemented
        return True

def test_thing():
    assert Thing() == snapshot()

    """).run_pytest(
        ["--inline-snapshot=create"],
        returncode=snapshot(1),
        changed_files=snapshot({"tests/test_something.py": """\
from inline_snapshot import snapshot

from inline_snapshot import HasRepr

class Thing:
    def __repr__(self):
        return "<something>"

    def __eq__(self,other):
        if not isinstance(other,Thing):
            return NotImplemented
        return True

def test_thing():
    assert Thing() == snapshot(HasRepr(Thing, "<something>"))

    \
"""}),
        outcomes={"passed": 1, "errors": 1},
    ).run_pytest(["--inline-snapshot=disable"]).run_pytest()


def test_hasrepr_type():
    assert 5 == HasRepr(int, "5")
    assert not "a" == HasRepr(int, "5")
    assert not HasRepr(float, "nan") == HasRepr(str, "nan")
    assert not HasRepr(str, "a") == HasRepr(str, "b")


def test_enum_in_dataclass():

    Example("""
from inline_snapshot import snapshot
from enum import Enum
from dataclasses import dataclass

class color(Enum):
    red="red"
    blue="blue"

@dataclass
class container:
    bg: color=color.red
    fg: color=color.blue

def test_a():
    assert container(bg=color.red,fg=color.red) == snapshot()
""").run_inline(
        ["--inline-snapshot=create"],
        changed_files=snapshot({"tests/test_something.py": """\

from inline_snapshot import snapshot
from enum import Enum
from dataclasses import dataclass

class color(Enum):
    red="red"
    blue="blue"

@dataclass
class container:
    bg: color=color.red
    fg: color=color.blue

def test_a():
    assert container(bg=color.red,fg=color.red) == snapshot(container(fg=color.red))
"""}),
    )


def test_flag():

    Example("""\
from inline_snapshot import snapshot
from enum import Flag, auto

class Color(Flag):
    red = auto()
    green = auto()
    blue = auto()

def test_a():
    assert Color.red | Color.blue == snapshot()
""").run_inline(
        ["--inline-snapshot=create"],
        changed_files=snapshot({"tests/test_something.py": """\
from inline_snapshot import snapshot
from enum import Flag, auto

class Color(Flag):
    red = auto()
    green = auto()
    blue = auto()

def test_a():
    assert Color.red | Color.blue == snapshot(Color.red | Color.blue)
"""}),
    )


def test_type():

    check_update(
        """\
class Color:
    pass

assert [Color,int] == snapshot()\
""",
        flags="create",
        expected_code="""\
class Color:
    pass

assert [Color,int] == snapshot([Color, int])\
""",
    )


def test_qualname():

    Example("""\
from enum import Enum
from inline_snapshot import snapshot


class Namespace:
    class Color(Enum):
        red="red"

def test():
    assert Namespace.Color.red == snapshot()

    """).run_inline(
        ["--inline-snapshot=create"],
        changed_files=snapshot({"tests/test_something.py": """\
from enum import Enum
from inline_snapshot import snapshot


class Namespace:
    class Color(Enum):
        red="red"

def test():
    assert Namespace.Color.red == snapshot(Namespace.Color.red)

    \
"""}),
    ).run_inline()


A = namedtuple("A", "a,b", defaults=[0])
B = namedtuple("B", "a,b", defaults=[0, 0])


class C(NamedTuple):
    a: int
    b: int = 0
    c: int = 0


@dataclass
class Dataclass:
    a: int
    b: int = dataclasses.field(default=0)
    c: list = dataclasses.field(default_factory=lambda: [])


default_dict = defaultdict(list)
default_dict[5].append(2)
default_dict[3].append(1)


@pytest.mark.parametrize(
    "d",
    [
        frozenset(["a"]),
        frozenset(),
        {"a"},
        set(),
        list(),
        ["a"],
        {},
        {1: "1"},
        (),
        (1,),
        (1, 2, 3),
        A(1, 2),
        A(1),
        A(0, 0),
        B(),
        B(b=5),
        C(1),
        C(1, 2),
        C(a=1, c=2),
        Dataclass(a=0, b=0, c=[]),
        Dataclass(a=1, b=2, c=[3]),
        default_dict,
        OrderedDict({1: 2, 3: 4}),
        UserDict({1: 2}),
        UserList([1, 2]),
        undefined,
    ],
)
def test_datatypes(d):
    code = code_repr(d)
    print("repr:     ", repr(d))
    print("code_repr:", code)
    assert d == eval(code)


def test_set():
    assert code_repr({1, 2, 3, "a", True, "b"}) == snapshot("{'a', 'b', 1, 2, 3}")
    assert code_repr({1j, 2j, 3j, "a", True, "b"}) == snapshot(
        "{'a', 'b', 1j, 2j, 3j, True}"
    )
    assert code_repr({1, 2, 3, 10, 11, 20, 200}) == snapshot(
        "{1, 2, 3, 10, 11, 20, 200}"
    )


def test_set_partial_order_is_deterministic():
    Example("""\
from itertools import permutations

from inline_snapshot import snapshot
from inline_snapshot._code_repr import code_repr


def test_set_partial_order_is_deterministic():
    a = frozenset({"a", "b"})
    b = frozenset({"a", "c"})
    c = frozenset({"a", "d"})

    for items in permutations([a, b, c]):
        assert set(items) == snapshot()
""").run_inline(
        ["--inline-snapshot=create"],
        changed_files={"tests/test_something.py": """\
from itertools import permutations

from inline_snapshot import snapshot
from inline_snapshot._code_repr import code_repr


def test_set_partial_order_is_deterministic():
    a = frozenset({"a", "b"})
    b = frozenset({"a", "c"})
    c = frozenset({"a", "d"})

    for items in permutations([a, b, c]):
        assert set(items) == snapshot(
            {frozenset({"a", "b"}), frozenset({"a", "c"}), frozenset({"a", "d"})}
        )
"""},
    )


def test_set_is_compared_atomically():
    Example("""\
from inline_snapshot import snapshot


def test_set_is_compared_atomically():
    assert {1, 2, 10} == snapshot({10, 1, 2})
""").run_inline(reported_categories=set())


def test_set_is_replaced_atomically():
    Example("""\
from inline_snapshot import snapshot


def test_set_is_replaced_atomically():
    assert {1, 2, 20} == snapshot({10, 1, 2})
""").run_inline(
        ["--inline-snapshot=fix"],
        changed_files={"tests/test_something.py": """\
from inline_snapshot import snapshot


def test_set_is_replaced_atomically():
    assert {1, 2, 20} == snapshot({1, 2, 20})
"""},
    )


def test_set_is_replaced_atomically_with_child_changes():
    Example("""\
from pathlib import Path

from inline_snapshot import snapshot


def test_set_is_replaced_atomically_with_child_changes():
    assert {Path("new")} == snapshot({Path("old")})
""").run_inline(
        ["--inline-snapshot=fix"],
        changed_files={"tests/test_something.py": """\
from pathlib import Path

from inline_snapshot import snapshot


def test_set_is_replaced_atomically_with_child_changes():
    assert {Path("new")} == snapshot({Path("new")})
"""},
    )


def test_set_is_reevaluated():
    Example("""\
from inline_snapshot import snapshot


def test_set_is_reevaluated():
    for value in [{1, 2}, {1, 2}]:
        assert value == snapshot({2, 1})
""").run_inline(reported_categories=set())


def test_set_custom_helpers():
    old = CustomSet([CustomCode(1, "1"), CustomCode(2, "2")])
    reordered = CustomSet([CustomCode(2, "2"), CustomCode(1, "1")])
    assert reeval(old, reordered) is reordered

    changed = CustomSet([CustomCode(3, "3")])
    with raises(
        snapshot(
            "UsageError: snapshot value should not change. Use Is(...) for dynamic snapshot parts."
        )
    ):
        reeval(old, changed)


def test_datatypes_explicit():
    assert code_repr(C(a=1, c=2)) == snapshot("C(a=1, c=2)")
    assert code_repr(B(b=5)) == snapshot("B(b=5)")
    assert code_repr(B(b=0)) == snapshot("B()")

    assert code_repr(Dataclass(a=0, b=0, c=[])) == snapshot("Dataclass(a=0)")
    assert code_repr(Dataclass(a=1, b=2, c=[3])) == snapshot(
        "Dataclass(a=1, b=2, c=[3])"
    )
    assert code_repr(Counter([1, 1, 1, 2])) == snapshot("Counter({1: 3, 2: 1})")

    assert code_repr(default_dict) == snapshot("defaultdict(list, {5: [2], 3: [1]})")


def test_fake_tuple1():

    class FakeTuple(tuple):
        _fields = 5

        def __repr__(self):
            return "FakeTuple()"

    assert code_repr(FakeTuple()) == snapshot("FakeTuple()")


def test_fake_tuple2():

    class FakeTuple(tuple):
        _fields = (1, 2)

        def __repr__(self):
            return "FakeTuple()"

    assert code_repr(FakeTuple()) == snapshot("FakeTuple()")


def test_invalid_repr():

    Example("""\
from inline_snapshot import snapshot

class Thing:
    def __repr__(self):
        return "+++"

    def __eq__(self,other):
        if not isinstance(other,Thing):
            return NotImplemented
        return True

def test_a():
    assert Thing() == snapshot()
""").run_inline(
        ["--inline-snapshot=create"],
        changed_files=snapshot({"tests/test_something.py": """\
from inline_snapshot import snapshot

from inline_snapshot import HasRepr

class Thing:
    def __repr__(self):
        return "+++"

    def __eq__(self,other):
        if not isinstance(other,Thing):
            return NotImplemented
        return True

def test_a():
    assert Thing() == snapshot(HasRepr(Thing, "+++"))
"""}),
    )


def test_function_type():

    Example("""\
from inline_snapshot import snapshot
from types import FunctionType
def func():...
def test():
    assert func == snapshot()
""").run_pytest(  # run with create flag and check the changed files
        ["--inline-snapshot=create"],
        changed_files=snapshot({"tests/test_something.py": """\
from inline_snapshot import snapshot
from types import FunctionType
def func():...
def test():
    assert func == snapshot(func)
"""}),
        returncode=snapshot(1),
        outcomes={"passed": 1, "errors": 1},
    )
