import os
import string
import sys
from collections import OrderedDict, namedtuple
from unittest.mock import MagicMock

import pytest

from devtools.ansi import strip_ansi
from devtools.prettier import PrettyFormat, pformat, pprint

try:
    import numpy
except ImportError:
    numpy = None

try:
    from multidict import CIMultiDict, MultiDict
except ImportError:
    CIMultiDict = None
    MultiDict = None

try:
    from asyncpg.protocol.protocol import _create_record as Record
except ImportError:
    Record = None


def test_dict():
    v = pformat({1: 2, 3: 4})
    print(v)
    assert v == (
        '{\n'
        '    1: 2,\n'
        '    3: 4,\n'
        '}')


def test_print(capsys):
    pprint({1: 2, 3: 4})
    stdout, stderr = capsys.readouterr()
    assert stdout == (
        '{\n'
        '    1: 2,\n'
        '    3: 4,\n'
        '}\n')
    assert stderr == ''


def test_colours():
    v = pformat({1: 2, 3: 4}, highlight=True)
    assert v.startswith('\x1b'), repr(v)
    v2 = strip_ansi(v)
    assert v2 == pformat({1: 2, 3: 4}), repr(v2)


def test_list():
    v = pformat(list(range(6)))
    assert v == (
        '[\n'
        '    0,\n'
        '    1,\n'
        '    2,\n'
        '    3,\n'
        '    4,\n'
        '    5,\n'
        ']')


def test_set():
    v = pformat(set(range(5)))
    assert v == (
        '{\n'
        '    0,\n'
        '    1,\n'
        '    2,\n'
        '    3,\n'
        '    4,\n'
        '}')


def test_tuple():
    v = pformat(tuple(range(5)))
    assert v == (
        '(\n'
        '    0,\n'
        '    1,\n'
        '    2,\n'
        '    3,\n'
        '    4,\n'
        ')')


def test_generator():
    v = pformat((i for i in range(3)))
    assert v == (
        '(\n'
        '    0,\n'
        '    1,\n'
        '    2,\n'
        ')')


def test_named_tuple():
    f = namedtuple('Foobar', ['foo', 'bar', 'spam'])
    v = pformat(f('x', 'y', 1))
    assert v == ("Foobar(\n"
                 "    foo='x',\n"
                 "    bar='y',\n"
                 "    spam=1,\n"
                 ")")


def test_generator_no_yield():
    pformat_ = PrettyFormat(yield_from_generators=False)
    v = pformat_((i for i in range(3)))
    assert v.startswith('<generator object test_generator_no_yield.<locals>.<genexpr> at ')


def test_str():
    pformat_ = PrettyFormat(width=12)
    v = pformat_(string.ascii_lowercase + '\n' + string.digits)
    print(repr(v))
    assert v == (
        "(\n"
        "    'abcde'\n"
        "    'fghij'\n"
        "    'klmno'\n"
        "    'pqrst'\n"
        "    'uvwxy'\n"
        "    'z\\n"
        "'\n"
        "    '01234'\n"
        "    '56789'\n"
        ")"
    )


def test_str_repr():
    pformat_ = PrettyFormat(repr_strings=True)
    v = pformat_(string.ascii_lowercase + '\n' + string.digits)
    assert v == "'abcdefghijklmnopqrstuvwxyz\\n0123456789'"


def test_bytes():
    pformat_ = PrettyFormat(width=12)
    v = pformat_(string.ascii_lowercase.encode())
    assert v == """(
    b'abcde'
    b'fghij'
    b'klmno'
    b'pqrst'
    b'uvwxy'
    b'z'
)"""


def test_short_bytes():
    assert "b'abcdefghijklmnopqrstuvwxyz'" == pformat(string.ascii_lowercase.encode())


@pytest.mark.skipif(numpy is None, reason='numpy not installed')
def test_indent_numpy():
    v = pformat({'numpy test': numpy.array(range(20))})
    assert v == """{
    'numpy test': (
        array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
               17, 18, 19])
    ),
}"""


@pytest.mark.skipif(numpy is None, reason='numpy not installed')
def test_indent_numpy_short():
    v = pformat({'numpy test': numpy.array(range(10))})
    assert v == """{
    'numpy test': array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
}"""


def test_ordered_dict():
    v = pformat(OrderedDict([(1, 2), (3, 4), (5, 6)]))
    print(v)
    assert v == """\
OrderedDict([
    (1, 2),
    (3, 4),
    (5, 6),
])"""


def test_frozenset():
    v = pformat(frozenset(range(3)))
    print(v)
    assert v == """\
frozenset({
    0,
    1,
    2,
})"""


def test_deep_objects():
    f = namedtuple('Foobar', ['foo', 'bar', 'spam'])
    v = pformat((
        (
            f('x', 'y', OrderedDict([(1, 2), (3, 4), (5, 6)])),
            frozenset(range(3)),
            [1, 2, {1: 2}]
        ),
        {1, 2, 3}
    ))
    print(v)
    assert v == """\
(
    (
        Foobar(
            foo='x',
            bar='y',
            spam=OrderedDict([
                (1, 2),
                (3, 4),
                (5, 6),
            ]),
        ),
        frozenset({
            0,
            1,
            2,
        }),
        [
            1,
            2,
            {1: 2},
        ],
    ),
    {1, 2, 3},
)"""


@pytest.mark.skipif(sys.version_info > (3, 5, 3), reason='like this only for old 3.5')
def test_call_args_py353():
    m = MagicMock()
    m(1, 2, 3, a=4)
    v = pformat(m.call_args)

    assert v == """\
_Call(
    (1, 2, 3),
    {'a': 4},
)"""


@pytest.mark.skipif(sys.version_info <= (3, 5, 3), reason='different for old 3.5')
def test_call_args_py354():
    m = MagicMock()
    m(1, 2, 3, a=4)
    v = pformat(m.call_args)

    assert v == """\
_Call(
    _fields=(1, 2, 3),
    {'a': 4},
)"""


@pytest.mark.skipif(MultiDict is None, reason='MultiDict not installed')
def test_multidict():
    d = MultiDict({'a': 1, 'b': 2})
    d.add('b', 3)
    v = pformat(d)
    assert set(v.split('\n')) == {
        "<MultiDict({",
        "    'a': 1,",
        "    'b': 2,",
        "    'b': 3,",
        "})>",
    }


@pytest.mark.skipif(CIMultiDict is None, reason='MultiDict not installed')
def test_cimultidict():
    v = pformat(CIMultiDict({'a': 1, 'b': 2}))
    assert set(v.split('\n')) == {
        "<CIMultiDict({",
        "    'a': 1,",
        "    'b': 2,",
        "})>",
    }


def test_os_environ():
    v = pformat(os.environ)
    assert v.startswith('<_Environ({')
    assert "    'HOME': '" in v


class Foo:
    a = 1

    def __init__(self):
        self.b = 2
        self.c = 3


def test_dir():
    assert pformat(vars(Foo())) == (
        "{\n"
        "    'b': 2,\n"
        "    'c': 3,\n"
        "}"
    )


def test_instance_dict():
    assert pformat(Foo().__dict__) == (
        "{\n"
        "    'b': 2,\n"
        "    'c': 3,\n"
        "}"
    )


def test_class_dict():
    s = pformat(Foo.__dict__)
    assert s.startswith('<mappingproxy({\n')
    assert "    '__module__': 'tests.test_prettier',\n" in s
    assert "    'a': 1,\n" in s
    assert s.endswith('})>')


def test_dictlike():
    class Dictlike:
        _d = {'x': 4, 'y': 42, 3: None}

        def items(self):
            yield from self._d.items()

        def __getitem__(self, item):
            return self._d[item]

    assert pformat(Dictlike()) == (
        "<Dictlike({\n"
        "    'x': 4,\n"
        "    'y': 42,\n"
        "    3: None,\n"
        "})>"
    )


@pytest.mark.skipif(Record is None, reason='asyncpg not installed')
def test_asyncpg_record():
    r = Record({'a': 0, 'b': 1}, (41, 42))
    assert dict(r) == {'a': 41, 'b': 42}
    assert pformat(r) == (
        "<Record({\n"
        "    'a': 41,\n"
        "    'b': 42,\n"
        "})>"
    )