#
import datetime
import re
import secrets
from urllib.parse import urlsplit

import jdatetime
import pytz
from django import forms
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.handlers.wsgi import WSGIRequest
from django.http import QueryDict
from django.template import Library, Node, defaulttags
from django.template.defaultfilters import date as django_date_filter
from django.templatetags.static import static
from django.utils import timezone
from django.utils.baseconv import base36
from django.utils.encoding import escape_uri_path, force_str
from django.utils.formats import number_format
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import get_language
from rolepermissions.roles import get_user_roles

from customrolepermissions.permissions import has_permission_exactly, Any, has_permission_exactly_with_cache, \
    AnyWithCache
from futplus.settings import TIME_ZONE
register = Library()


@register.filter
def startswith(value, sub_value):
    try:
        return value.startswith(force_str(sub_value))
    except:  # noqa
        return False


@register.filter(name='int')
def to_int(val):
    try:
        return int(val)
    except ValueError:
        return val


@register.filter(name='float')
def to_float(val):
    try:
        return float(val)
    except ValueError:
        return val


@register.filter(name='str')
def to_str(val):
    return str(val) if val else ''


@register.filter
def to_list(value):
    """
    Return the value turned into a list.
    normally used to change queryset to list
    """
    return list(value)


@register.filter
def pretty_float(value):
    return number_format(('%.5f' % value).rstrip('0').rstrip('.'))


@register.filter(name='add')
def add_filter(val, arg):
    return val + arg


@register.filter(name='sub')
def sub_filter(val, arg):
    return val - arg


@register.filter
def mul(value, arg0):
    if value == 0:
        return value
    if value is None:
        return None
    return value * arg0


@register.filter
def getitem(value, key, default=None):
    try:
        return value[key]
    except (TypeError, KeyError):
        return getattr(value, key)
    except AttributeError:
        return default


@register.filter
def is_absolute_url(path):
    return path.startswith(('https://', 'http://', '//'))


@register.filter
def absolute_or_static(path: str):
    if is_absolute_url(path):
        return path
    else:
        return static(path)


@register.simple_tag(name='img-or-pixel')
def img_or_pixel(image, image_css_class='', pixel_css_class='', alt=''):
    if image:
        res = '<img src="{image.url}" class="{image_css_class}" alt="{alt}">' \
            .format(image=image, image_css_class=image_css_class, alt=alt)
    else:
        res = '<img src="{pixel}" class="{pixel_css_class}" alt="{alt}">' \
            .format(pixel=static('images/pixel.png'), pixel_css_class=pixel_css_class, alt=alt)

    return mark_safe(res)  # nosec


@register.simple_tag(name='img-or-pixel-lazy')
def img_or_pixel_lazy(image, image_css_class='', pixel_css_class='', alt=''):
    if image:
        res = '<img src="{pixel}" data-src="{image.url}" class="lazyload {image_css_class}" alt="{alt}">' \
            .format(pixel=static('images/pixel.png'), image=image, image_css_class=image_css_class, alt=alt)
    else:
        res = '<img src="{pixel}" class="{pixel_css_class}" alt="{alt}">' \
            .format(pixel=static('images/pixel.png'), pixel_css_class=pixel_css_class, alt=alt)

    return mark_safe(res)  # nosec


@register.simple_tag(name='thumbnail-img-or-pixel')
def thumbnail_img_or_pixel(image, thumbnail_css_class='', image_css_class='', pixel_css_class='', alt=''):
    if image:
        img = '<img src="{image.url}" class="{image_css_class}" alt="{alt}">' \
            .format(image=image, image_css_class=image_css_class, alt=alt)
    else:
        img = '<img src="{pixel}" class="{pixel_css_class}" alt="{alt}">' \
            .format(pixel=static('images/pixel.png'), pixel_css_class=pixel_css_class, alt=alt)

    res = '<div class="thumbnail {thumbnail_css_class}">{img}</div>'.format(
        img=img, thumbnail_css_class=thumbnail_css_class)

    return mark_safe(res)  # nosec


# @register.simple_tag(name='profile-avatar-img')
# def profile_avatar_img(profile: Account, css_class='avatar', title=None, large=False):
#     image_url = profile.get_avatar_url(large)
#     if title is None:
#         title = profile.user.get_full_name()
#     res = f'<img src="{image_url}" class="{css_class}" title="{title}">'
#     return mark_safe(res)  # nosec


@register.filter()
def split(value, sep=None):
    try:
        return value.split(sep)
    except TypeError:
        return [value, ]


class OptionalURLNode(defaulttags.URLNode):
    def render(self, context):
        for k in list(self.kwargs.keys()):
            if not self.kwargs[k].resolve(context):
                self.kwargs.pop(k)
        return super().render(context)


@register.tag(name='optional-url')
def optional_url(parser, token):
    urlnode = defaulttags.url(parser, token)
    return OptionalURLNode(urlnode.view_name, urlnode.args, urlnode.kwargs, urlnode.asvar, )


@register.tag(name='fixspace')
def do_fixspace(parser, token):
    nodelist = parser.parse(('endfixspace',))
    parser.delete_first_token()
    return FixspaceNode(nodelist)


class FixspaceNode(Node):
    remove_whitespace_re = re.compile(r'\s\s+')

    def __init__(self, nodelist):
        self.nodelist = nodelist

    def render(self, context):
        output = self.nodelist.render(context)
        return self.clean(output)

    def clean(self, content):
        if not content:
            return content

        content = self.remove_whitespace_re.sub(' ', str(content).strip())
        return content


@register.tag(name='fixemptylines')
def do_fixemptylines(parser, token):
    nodelist = parser.parse(('endfixemptylines',))
    parser.delete_first_token()
    return FixemptylinesNode(nodelist)


class FixemptylinesNode(Node):
    emtpylines_re = re.compile(r'\n\s*\n')

    def __init__(self, nodelist):
        self.nodelist = nodelist

    def render(self, context):
        output = self.nodelist.render(context)
        return self.clean(output)

    def clean(self, content):
        if not content:
            return content
        content = self.emtpylines_re.sub('\n', str(content).strip())
        return content


@register.simple_tag(name='unique-id')
def unique_id(length=6, namespace=''):
    # generate unique id like Foundation.GetYoDigits()
    length = length or 6
    i = round(pow(36, length + 1) - secrets.randbelow(pow(36, length)))
    return (f'{namespace}-' if namespace else '') + base36.encode(i)[1:]


@register.filter
def unique_ids(form: forms.BaseForm):
    assert isinstance(form, forms.BaseForm)  # nosec

    uid = unique_id(6)
    for name, field in form.fields.items():  # type:forms.Field
        field.widget.attrs['id'] = '{}-{}'.format(name, uid)

    return form


@register.simple_tag(takes_context=True, name='canonical-url')
def canonical_url_tag(context, url=None, url_only=False):
    assert 'request' in context, 'canonical-url needs request context'  # nosec
    # from django.core.handlers.wsgi import WSGIRequest
    request = context['request']  # type: WSGIRequest

    if url:
        path = url.split('?', 1)[0]
        querystring_dict = QueryDict(urlsplit(url).query)
    else:
        path = escape_uri_path(request.path)
        querystring_dict = request.GET

    url = '{}{}'.format(path, '/' if not path.endswith('/') else '')
    url = request.build_absolute_uri(url)

    # allow only limited variables to canonical_url query_string
    qs = {}

    page = querystring_dict.get('page')
    if page and page != '1':  # page=1 is same as page variable absense
        qs['page'] = page

    if qs:
        url = '{}?{}'.format(url, '&'.join([f'{k}={v}' for (k, v) in qs.items()]))

    if url_only:
        return url

    return mark_safe(f'<link rel="canonical" href="{url}"/>')  # nosec


@register.filter
def target_blank(value):
    if value:
        return mark_safe(force_str(value).replace('<a href=', '<a target="_blank" href='))  # nosec
    return value


class DeferMedia(forms.Media):
    def render_js(self):
        return [
            format_html(
                '<script defer type="text/javascript" src="{}"></script>',
                self.absolute_path(path)
            ) for path in self._js
        ]


@register.filter(name='js_defer')
def js_defer(media: forms.Media):
    if isinstance(media, forms.Media):
        return DeferMedia(css=media._css, js=media._js)
    else:
        return media


@register.simple_tag
def merge_media(form, inlines):
    media = forms.Media()
    if form:
        media += form.media
    if inlines:
        for formset in inlines:
            media += formset.media

        extra_js = []
        if 'django_select2/django_select2.js' in media._js:
            extra_js.append('django_select2/django_select2.js')
        # extra_js.append('js/jquery.formset.js')
        extra_js.append('bootstrap/js/jquery.formset.js')
        media += forms.Media(js=extra_js)
    return media


@register.filter
def is_active_lang(lang_code):
    return lang_code.lower() in settings.SITE_LANGUAGES


@register.filter
def build_absolute_uri(location, requst):
    if isinstance(requst, Site):
        scheme = 'https'
        return f'{scheme}://{requst.domain}{location}'
    else:
        return requst.build_absolute_uri(location)


@register.filter
def dir_auto(value):
    return force_str(value or '').replace('<p>', '<p dir="auto">')


@register.filter(expects_localtime=True, is_safe=False)
def l10n_date(value, arg=None):
    if value in (None, ''):
        return ''
    lang_code = get_language()
    if lang_code == 'fa':
        value = jdatetime.datetime.fromgregorian(datetime=value)

    return django_date_filter(value, arg)


@register.filter(expects_localtime=True, is_safe=False)
def l10n_date_custom(value, arg=None):
    if value in (None, ''):
        return ''
    lang_code = get_language()
    if lang_code == 'fa':
        value = jdatetime.datetime.fromgregorian(datetime=value)
    return django_date_filter(value.astimezone(pytz.timezone(TIME_ZONE)), arg)


@register.filter(name='can')
def can(user, permission):
    # return has_permission_exactly(user, permission)
    return has_permission_exactly_with_cache(user, permission)


@register.filter(name='can_any')
def can_any(user, permissions_str):
    # return Any(*permissions_str.split(','))(user)
    return AnyWithCache(*permissions_str.split(','))(user)


@register.filter
def user_roles(user):
    role_names = ', '.join(
        force_str(role.verbose_name)
        for role in sorted(get_user_roles(user), key=lambda role: role.order)
        if (getattr(role, 'selectable_role', True) and getattr(role, 'visible', True))
    )
    return role_names


