import hashlib
import re
from urllib.parse import urlencode, urljoin

import dns.resolver
from django.utils.module_loading import import_string

from avatar.conf import settings
from avatar.utils import force_bytes, get_default_avatar_url, get_primary_avatar

# If the FacebookAvatarProvider is used, a mechanism needs to be defined on
# how to obtain the user's Facebook UID. This is done via
# ``AVATAR_FACEBOOK_GET_ID``.
get_facebook_id = None

if "avatar.providers.FacebookAvatarProvider" in settings.AVATAR_PROVIDERS:
    if callable(settings.AVATAR_FACEBOOK_GET_ID):
        get_facebook_id = settings.AVATAR_FACEBOOK_GET_ID
    else:
        get_facebook_id = import_string(settings.AVATAR_FACEBOOK_GET_ID)


class DefaultAvatarProvider(object):
    """
    Returns the default url defined by ``settings.DEFAULT_AVATAR_URL``.
    """

    @classmethod
    def get_avatar_url(cls, user, width, height=None):
        return get_default_avatar_url()


class PrimaryAvatarProvider(object):
    """
    Returns the primary Avatar from the users avatar set.
    """

    @classmethod
    def get_avatar_url(cls, user, width, height=None):
        if not height:
            height = width
        avatar = get_primary_avatar(user, width, height)
        if avatar:
            return avatar.avatar_url(width, height)


class GravatarAvatarProvider(object):
    """
    Returns the url for an avatar by the Gravatar service.
    """

    @classmethod
    def get_avatar_url(cls, user, width, _height=None):
        params = {"s": str(width)}
        if settings.AVATAR_GRAVATAR_DEFAULT:
            params["d"] = settings.AVATAR_GRAVATAR_DEFAULT
        if settings.AVATAR_GRAVATAR_FORCEDEFAULT:
            params["f"] = "y"
        path = "%s/?%s" % (
            hashlib.md5(
                force_bytes(getattr(user, settings.AVATAR_GRAVATAR_FIELD))
            ).hexdigest(),
            urlencode(params),
        )

        return urljoin(settings.AVATAR_GRAVATAR_BASE_URL, path)


class LibRAvatarProvider:
    """
    Returns the url of an avatar by the LibRavatar service.
    """

    @classmethod
    def get_avatar_url(cls, user, width, _height=None):
        email = getattr(user, settings.AVATAR_GRAVATAR_FIELD).encode("utf-8")
        try:
            _, domain = email.split(b"@")
            answers = dns.resolver.query("_avatars._tcp." + domain, "SRV")
            hostname = re.sub(r"\.$", "", str(answers[0].target))
            # query returns "example.com." and while http requests are fine with this,
            # https most certainly do not consider "example.com." and "example.com" to be the same.
            port = str(answers[0].port)
            if port == "443":
                baseurl = "https://" + hostname + "/avatar/"
            else:
                baseurl = "http://" + hostname + ":" + port + "/avatar/"
        except Exception:
            baseurl = "https://seccdn.libravatar.org/avatar/"

        params = {"s": str(width)}
        if settings.AVATAR_GRAVATAR_DEFAULT:
            params["d"] = settings.AVATAR_GRAVATAR_DEFAULT
        if settings.AVATAR_GRAVATAR_FORCEDEFAULT:
            params["f"] = "y"
        path = "%s/?%s" % (
            hashlib.md5(force_bytes(email.strip().lower())).hexdigest(),
            urlencode(params),
        )
        return urljoin(baseurl, path)


class FacebookAvatarProvider(object):
    """
    Returns the url of a Facebook profile image.
    """

    @classmethod
    def get_avatar_url(cls, user, width, height=None):
        if not height:
            height = width
        fb_id = get_facebook_id(user)
        if fb_id:
            url = "https://graph.facebook.com/{fb_id}/picture?type=square&width={width}&height={height}"
            return url.format(fb_id=fb_id, width=width, height=height)


class InitialsAvatarProvider(object):
    """
    Returns a tuple with template_name and context for rendering the given user's avatar as their
    initials in white against a background with random hue based on their primary key.
    """

    @classmethod
    def get_avatar_url(cls, user, width, _height=None):
        initials = user.first_name[:1] + user.last_name[:1]
        if not initials:
            initials = user.username[:1]
        initials = initials.upper()
        context = {
            "fontsize": (width * 1.1) / 2,
            "initials": initials,
            "hue": user.pk % 360,
            "saturation": "65%",
            "lightness": "60%",
        }
        return ("avatar/initials.html", context)
