import datetime
import json
import re
from collections import namedtuple

import redis
from django.conf import settings
from django.core.cache import caches
from django.core.exceptions import ImproperlyConfigured
from django.dispatch import receiver
from django.test.signals import setting_changed
from django.utils.crypto import get_random_string
from django.utils.timezone import utc

_client = None      # Cached instance of the Redis client.

_settings = None    # Cached dict of userlog settings


@receiver(setting_changed)
def reset_caches(**kwargs):
    global _client, _settings
    if kwargs['setting'] == 'CACHES':
        _client = None
        _settings = None


def get_redis_client():
    global _client

    if _client is not None:
        return _client

    try:
        cache = caches['userlog']
    except KeyError:
        raise ImproperlyConfigured("No 'userlog' cache found in CACHES.")

    try:
        try:
            _client = cache.client                  # django-redis
        except AttributeError:
            _client = cache.get_master_client()     # django-redis-cache
        assert isinstance(_client, redis.StrictRedis)
    except (AssertionError, AttributeError):
        raise ImproperlyConfigured("'userlog' cache doesn't use Redis.")

    return _client


def convert_timestamp(ts):
    if settings.USE_TZ:
        return datetime.datetime.utcfromtimestamp(ts).replace(tzinfo=utc)
    else:
        return datetime.datetime.fromtimestamp(ts)


def get_log(username):
    """
    Return a list of page views.

    Each item is a dict with `datetime`, `method`, `path` and `code` keys.
    """
    redis = get_redis_client()
    log_key = 'log:{}'.format(username)
    raw_log = redis.lrange(log_key, 0, -1)
    log = []
    for raw_item in raw_log:
        item = json.loads(raw_item.decode())
        item['datetime'] = convert_timestamp(item.pop('time'))
        log.append(item)
    return log


def get_token(username, length=20, timeout=20):
    """
    Obtain an access token that can be passed to a websocket client.
    """
    redis = get_redis_client()
    token = get_random_string(length)
    token_key = 'token:{}'.format(token)
    redis.set(token_key, username)
    redis.expire(token_key, timeout)
    return token


UserLogSettings = namedtuple(
    'UserLogSettings',
    ['timeout', 'max_size', 'publish', 'ignore_urls', 'websocket_address'])


def get_userlog_settings():
    global _settings

    if _settings is not None:
        return _settings

    def get_setting(name, default):
        return getattr(settings, 'USERLOG_' + name, default)

    # Coerce values into expected types in order to detect invalid settings.
    _settings = UserLogSettings(
        # Hardcode the default timeout because it isn't exposed by Django.
        timeout=int(settings.CACHES['userlog'].get('TIMEOUT', 300)),
        max_size=int(get_setting('MAX_SIZE', 25)),
        publish=bool(get_setting('PUBLISH', True)),
        ignore_urls=[re.compile(pattern)
                     for pattern in get_setting('IGNORE_URLS', [])],
        websocket_address=str(get_setting('WEBSOCKET_ADDRESS',
                                          'ws://localhost:8080/')),
    )

    return _settings