# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals

import ast
import re
import struct
from decimal import Decimal
from pprint import pprint

import pytest
from case import skip

from celery.five import (items, long_t, python_2_unicode_compatible, text_t,
                         values)
from celery.utils.saferepr import saferepr

D_NUMBERS = {
    b'integer': 1,
    b'float': 1.3,
    b'decimal': Decimal('1.3'),
    b'long': long_t(4),
    b'complex': complex(13.3),
}
D_INT_KEYS = {v: k for k, v in items(D_NUMBERS)}

QUICK_BROWN_FOX = 'The quick brown fox jumps over the lazy dog.'
B_QUICK_BROWN_FOX = b'The quick brown fox jumps over the lazy dog.'

D_TEXT = {
    b'foo': QUICK_BROWN_FOX,
    b'bar': B_QUICK_BROWN_FOX,
    b'baz': B_QUICK_BROWN_FOX,
    b'xuzzy': B_QUICK_BROWN_FOX,
}

L_NUMBERS = list(values(D_NUMBERS))

D_TEXT_LARGE = {
    b'bazxuzzyfoobarlongverylonglong': QUICK_BROWN_FOX * 30,
}

D_ALL = {
    b'numbers': D_NUMBERS,
    b'intkeys': D_INT_KEYS,
    b'text': D_TEXT,
    b'largetext': D_TEXT_LARGE,
}

D_D_TEXT = {b'rest': D_TEXT}

RE_OLD_SET_REPR = re.compile(r'(?<!frozen)set\([\[|\{](.+?)[\}\]]\)')
RE_OLD_SET_REPR_REPLACE = r'{\1}'
RE_OLD_SET_CUSTOM_REPR = re.compile(r'((?:frozen)?set\d?\()\[(.+?)\](\))')
RE_OLD_SET_CUSTOM_REPR_REPLACE = r'\1{\2}\3'
RE_EMPTY_SET_REPR = re.compile(r'((?:frozen)?set\d?)\(\[\]\)')
RE_EMPTY_SET_REPR_REPLACE = r'\1()'
RE_LONG_SUFFIX = re.compile(r'(\d)+L')


def old_repr(s):
    return text_t(RE_LONG_SUFFIX.sub(
        r'\1',
        RE_EMPTY_SET_REPR.sub(
            RE_EMPTY_SET_REPR_REPLACE,
            RE_OLD_SET_REPR.sub(
                RE_OLD_SET_REPR_REPLACE,
                RE_OLD_SET_CUSTOM_REPR.sub(
                    RE_OLD_SET_CUSTOM_REPR_REPLACE,
                    repr(s).replace("u'", "'"),
                )
            ),
        ),
    )).replace('set([])', 'set()')


class list2(list):
    pass


@python_2_unicode_compatible
class list3(list):

    def __repr__(self):
        return list.__repr__(self)


class tuple2(tuple):
    pass


@python_2_unicode_compatible
class tuple3(tuple):

    def __repr__(self):
        return tuple.__repr__(self)


class set2(set):
    pass


@python_2_unicode_compatible
class set3(set):

    def __repr__(self):
        return set.__repr__(self)


class frozenset2(frozenset):
    pass


@python_2_unicode_compatible
class frozenset3(frozenset):

    def __repr__(self):
        return frozenset.__repr__(self)


class dict2(dict):
    pass


@python_2_unicode_compatible
class dict3(dict):

    def __repr__(self):
        return dict.__repr__(self)


class test_saferepr:

    @pytest.mark.parametrize('value', list(values(D_NUMBERS)))
    def test_safe_types(self, value):
        assert saferepr(value) == old_repr(value)

    def test_numbers_dict(self):
        assert saferepr(D_NUMBERS) == old_repr(D_NUMBERS)

    def test_numbers_list(self):
        assert saferepr(L_NUMBERS) == old_repr(L_NUMBERS)

    def test_numbers_keys(self):
        assert saferepr(D_INT_KEYS) == old_repr(D_INT_KEYS)

    def test_text(self):
        assert saferepr(D_TEXT) == old_repr(D_TEXT).replace("u'", "'")

    def test_text_maxlen(self):
        assert saferepr(D_D_TEXT, 100).endswith("...', ...}}")

    def test_maxlevels(self):
        saferepr(D_ALL, maxlevels=1)

    def test_recursion(self):
        d = {1: 2, 3: {4: 5}}
        d[3][6] = d
        res = saferepr(d)
        assert 'Recursion on' in res

    @pytest.mark.parametrize('value', [
        0, 0, 0 + 0j, 0.0, '', b'',
        (), tuple2(), tuple3(),
        [], list2(), list3(),
        set(), set2(), set3(),
        frozenset(), frozenset2(), frozenset3(),
        {}, dict2(), dict3(),
        test_recursion, pprint,
        -6, -6, -6 - 6j, -1.5, 'x', b'x', (3,), [3], {3: 6},
        (1, 2), [3, 4], {5: 6},
        tuple2((1, 2)), tuple3((1, 2)), tuple3(range(100)),
        [3, 4], list2([3, 4]), list3([3, 4]), list3(range(100)),
        {7}, set2({7}), set3({7}),
        frozenset({8}), frozenset2({8}), frozenset3({8}),
        dict2({5: 6}), dict3({5: 6}),
        range(10, -11, -1)
    ])
    def test_same_as_repr(self, value):
        # Simple objects, small containers, and classes that overwrite __repr__
        # For those the result should be the same as repr().
        # Ahem.  The docs don't say anything about that -- this appears to
        # be testing an implementation quirk.  Starting in Python 2.5, it's
        # not true for dicts:  pprint always sorts dicts by key now; before,
        # it sorted a dict display if and only if the display required
        # multiple lines.  For that reason, dicts with more than one element
        # aren't tested here.
        native = old_repr(value)
        assert saferepr(value) == native

    def test_single_quote(self):
        val = {"foo's": "bar's"}
        assert ast.literal_eval(saferepr(val)) == val

    @skip.if_python3()
    def test_bytes_with_unicode(self):
        class X(object):

            def __repr__(self):
                return 'æ e i a æ å'.encode(
                    'utf-8', errors='backslash replace')

        val = X()
        assert repr(val)
        assert saferepr(val)

    @skip.unless_python3()
    def test_unicode_bytes(self):
        val = 'øystein'.encode('utf-8')
        assert saferepr(val) == "b'øystein'"

    @skip.unless_python3()
    def test_unicode_bytes__long(self):
        val = 'øystein'.encode('utf-8') * 1024
        assert saferepr(val, maxlen=128).endswith("...'")

    @skip.unless_python3()
    def test_binary_bytes(self):
        val = struct.pack('>QQQ', 12223, 1234, 3123)
        if hasattr(bytes, 'hex'):  # Python 3.5+
            assert '2fbf' in saferepr(val, maxlen=128)
        else:  # Python 3.4
            assert saferepr(val, maxlen=128)

    @skip.unless_python3()
    def test_binary_bytes__long(self):
        val = struct.pack('>QQQ', 12223, 1234, 3123) * 1024
        result = saferepr(val, maxlen=128)
        if hasattr(bytes, 'hex'):  # Python 3.5+
            assert '2fbf' in result
            assert result.endswith("...'")
        else:  # Python 3.4
            assert result

    def test_repr_raises(self):
        class O(object):
            def __repr__(self):
                raise KeyError('foo')
        assert 'Unrepresentable' in saferepr(O())

    def test_bytes_with_unicode_py2_and_3(self):
        assert saferepr([b'foo', 'a®rgs'.encode('utf-8')])
