import re
import logging
from urllib.parse import urlparse
from socket import gethostbyaddr, herror
import IPy


from .models import Token
from .settings import (
    CSRF_DOMAINS,
    TOKEN_VAR,
    TOKEN_HEADER,
)
from .utils import cache

logger = logging.getLogger('django_api_auth')

__all__ = (
    'token_is_valid', 'referer_is_valid', 'is_oauth', 'is_authenticated'
)


def _map_ipv6_on_ipv4(ip):
    """Пытаемся преобразовать ipv6-адрес в ipv4. Если не удалось,
    возвращаем то что пришло.
    http://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses
    """
    ip_ = IPy.IP(ip)
    if ip_.version() == 6:
        try:
            return str(ip_.v46map())
        except ValueError:
            pass
    return ip


def _extract_token_name(request):
    token_name = request.POST.get(TOKEN_VAR, request.GET.get(TOKEN_VAR))
    if token_name is None:
        token_name = request.META.get(TOKEN_HEADER)
    if token_name is None:
        logger.warning('No token name for request')
        return None
    return token_name


def extract_ips(request):
    ips = []
    ip = request.META.get('HTTP_X_REAL_IP')
    if ip:
        ips.append(_map_ipv6_on_ipv4(ip))
    ip = request.META['REMOTE_ADDR']
    ips.append(_map_ipv6_on_ipv4(ip))
    return ips


def ip_in_ips_or_hosts(ip, ips_list, hostnames_list):
    if ip in ips_list:
        logger.debug('Allowed access for ip "%s" by token', ip)
        return True

    try:
        host = gethostbyaddr(ip)[0]
    except (IndexError, herror):
        return False

    if host in hostnames_list:
        logger.debug('Allowed access for hostname "%s" by token', host)
        return True


def _get_token_from_db(token_name):
    try:
        return Token.objects.get(token=token_name)
    except Token.DoesNotExist:
        logger.info('No such token name "%s"', token_name)


def token_is_valid(request):
    token_name = _extract_token_name(request)
    if token_name is None:
        return False

    ips = extract_ips(request)

    for ip in ips:
        if cache.get(token_name, ip):
            return True

    logger.debug(
        'Cache miss for token (no token shown for security reason)'
    )

    token = _get_token_from_db(token_name)

    if token is not None:
        for ip in ips:
            access = ip_in_ips_or_hosts(
                ip=ip,
                ips_list=token.ips_list,
                hostnames_list=token.hostnames_list,
            )
            cache.set(token_name, ip, access)
            if access:
                return True

    logger.info(
        'Machine with ips "%s" is not allowed to use token "%s"',
        ', '.join(ips),
        token_name,
    )

    return False


def referer_is_valid(request):
    referer = request.META.get('HTTP_REFERER')
    if referer is None:
        return False
    hostname = urlparse(referer).hostname
    return hostname and any(hostname.endswith(d) for d in CSRF_DOMAINS)


oauth_re = re.compile(r"OAuth (.+)")


def is_oauth(request):
    oauth_token = request.META.get('HTTP_AUTHORIZATION')
    return bool(oauth_token and oauth_re.match(oauth_token))


def is_authenticated(request):
    return hasattr(request, 'user') and request.user.is_authenticated()
