import importlib
import pkgutil
import functools
import hashlib
import json
import os
import re
import time
import uuid
import xml.etree.cElementTree as xml_et
import zlib

import aiohttp
from django.core.exceptions import ValidationError
from django.utils import timezone

from .aviews import JsonEncoder

r_uuid4 = (r'[a-f0-9]{8}-?'
           r'[a-f0-9]{4}-?'
           r'4[a-f0-9]{3}-?'
           r'[89ab][a-f0-9]{3}-?'
           r'[a-f0-9]{12}')

re_uuid4 = re.compile(r_uuid4)

re_class_name = re.compile(r'([A-Z]*[a-z]*)')


def now(ms=True, ts=False):
    if ts:
        n = time.time()
        if not ms:
            n = int(n)
    else:
        n = timezone.now()
        if not ms:
            n = n.replace(microsecond=0)
    return n


def get_uuid4(s, match=True):
    if match:
        m = re_uuid4.match(s)
    else:
        m = re_uuid4.search(s)
    if not m:
        return
    return uuid.UUID(m.group(0))


def get_hash(data: str) -> str:
    return str(abs(zlib.crc32(data.encode())))


def import_module_from_all_apps(apps_path, module):
    """Imports all the modules from apps directory"""
    # Импортируем "module" из всех приложений.
    for dir_name in os.listdir(apps_path):
        package_dir = os.path.join(apps_path, dir_name)
        if os.path.isdir(package_dir):
            init = os.path.join(package_dir, '__init__.py')
            if not os.path.exists(init) or os.path.isdir(init):
                continue
            try:
                importlib.import_module('{}.{}'.format(dir_name, module))
            except ImportError:
                pass


def import_modules_from_packages(package, module):
    """Imports all the apps from package"""
    for importer, modname, ispkg in pkgutil.iter_modules(package.__path__):
        if ispkg:
            try:
                importlib.import_module('{}.{}.{}'.format(package.__name__, modname, module))
            except ImportError:
                pass


def convert_class_name(name):
    """
    >>> convert_class_name('ClassName')
    'class_name'
    >>> convert_class_name('ABClassName')
    'abclass_name'
    """
    l = re_class_name.findall(name)
    return '_'.join(i.lower() for i in l if i)


def int_or_zero(v):
    """
    Convert object to int
    """
    if isinstance(v, str):
        v = v.strip()
    try:
        return int(v)
    except (ValueError, TypeError):
        return 0


def enum_to_choice(v):
    translation = v.translation() if hasattr(v, 'translation') else {}
    return [
        (a.value, translation.get(a, a.name))
        for a in v
    ]


def hash_data(data):
    """
    >>> hash_data({'h': 123, 'f': 43})
    'fcc217e2223b1291abf6e58a6e656bfd'
    >>> hash_data({'f': 43, 'h': 123})
    'fcc217e2223b1291abf6e58a6e656bfd'
    >>> hash_data(['h', 43])
    'e2de391295334b9b27272e6517ed047a'
    """
    m = hashlib.md5()
    m.update(json.dumps(
        data, sort_keys=True, cls=JsonEncoder,
    ).encode())
    return m.hexdigest()


def import_class(py_path):
    path, class_name = py_path.rsplit('.', 1)
    module = importlib.import_module(path)
    return getattr(module, class_name)


def method_client_once(func):
    @functools.wraps(func)
    async def wraper(self, *args, **kwargs):
        if kwargs.get('client') is None:
            with aiohttp.ClientSession() as client:
                kwargs['client'] = client
                return await func(self, *args, **kwargs)
        else:
            return await func(self, *args, **kwargs)
    return wraper


def _merge(a, b, path=None):
    """merges b into a"""
    if path is None:
        path = []
    for key, bv in b.items():
        if key in a:
            av = a[key]
            nested_path = path + [key]
            if isinstance(av, dict) and isinstance(bv, dict):
                _merge(av, bv, nested_path)
                continue
        a[key] = bv
    return a


def merge(*args, **kwargs):
    args = ({},) + args + (kwargs,)
    return functools.reduce(_merge, args)


def query_bool(data):
    if data in (True, 'true', 'True', 1, '1', 'yes', 'on'):
        return True
    elif data in (False, 'false', 'False', 0, '0', 'no', 'off'):
        return False


def get_request_from_parameters(*args, **kwargs):
    if kwargs.get('request') is not None:
        return kwargs['request']
    for i in args:
        if hasattr(i, 'request'):
            return i.request
        elif hasattr(i, 'rel_url'):
            return i
    else:
        raise ValueError(*args, **kwargs)


def get_app_from_parameters(*args, **kwargs):
    if kwargs.get('request') is not None:
        return kwargs['request'].app
    for i in args:
        if hasattr(i, 'app'):
            return i.app
        elif hasattr(i, 'request'):
            return i.request.app
        elif hasattr(i, '_context'):
            return i._context.app


def validate_svg_file(f):
    tag = None
    f.seek(0)
    try:
        for event, el in xml_et.iterparse(f, ('start',)):
            tag = el.tag
            break
    except xml_et.ParseError:
        pass

    if tag != '{http://www.w3.org/2000/svg}svg':
        raise ValidationError('Uploaded file is not an image or SVG file')

    f.seek(0)
    return f