"""
LRU-Cache implementation for formatted (`format=`) answers
"""

import datetime
import re
import time
import os
import hashlib
import random

import pytz
import pylru

from globals import LRU_CACHE

CACHE_SIZE = 10000
CACHE = pylru.lrucache(CACHE_SIZE)

# strings longer than this are stored not in ram
# but in the file cache
MIN_SIZE_FOR_FILECACHE = 80

def _update_answer(answer):
    def _now_in_tz(timezone):
        return datetime.datetime.now(pytz.timezone(timezone)).strftime("%H:%M:%S%z")

    if isinstance(answer, str) and "%{{NOW(" in answer:
        answer = re.sub(r"%{{NOW\(([^}]*)\)}}", lambda x: _now_in_tz(x.group(1)), answer)

    return answer

def get_signature(user_agent, query_string, client_ip_address, lang):
    """
    Get cache signature based on `user_agent`, `url_string`,
    `lang`, and `client_ip_address`
    Return `None` if query should not be cached.
    """

    if "?" in query_string:
        location = query_string.split("?", 1)[0]
    else:
        location = query_string
    if location.startswith("http://"):
        location = location[7:]
    elif location.startswith("https://"):
        location = location[8:]
    if ":" in location:
        return None

    signature = "%s:%s:%s:%s" % \
        (user_agent, query_string, client_ip_address, lang)
    print(signature)
    return signature

def get(signature):
    """
    If `update_answer` is not True, return answer as it is
    stored in the cache. Otherwise update it, using
    the `_update_answer` function.
    """

    if not signature:
        return None

    value_record = CACHE.get(signature)
    if not value_record:
        return None

    value = value_record["val"]
    expiry = value_record["expiry"]
    if value and time.time() < expiry:
        if value.startswith("file:") or value.startswith("bfile:"):
            value = _read_from_file(signature, sighash=value)
            if not value:
                return None
        return _update_answer(value)
    return None

def _randint(minimum, maximum):
    return random.randrange(maximum - minimum)

def store(signature, value):
    """
    Store in cache `value` for `signature`
    """
    if not signature:
        return _update_answer(value)

    if len(value) >= MIN_SIZE_FOR_FILECACHE:
        value_to_store = _store_in_file(signature, value)
    else:
        value_to_store = value

    value_record = {
        "val": value_to_store,
        "expiry": time.time() + _randint(1000, 2000),
        }

    CACHE[signature] = value_record

    return _update_answer(value)

def _hash(signature):
    return hashlib.md5(signature.encode("utf-8")).hexdigest()

def _store_in_file(signature, value):
    """Store `value` for `signature` in cache file.
    Return file name (signature_hash) as the result.
    `value` can be string as well as bytes.
    Returned filename is prefixed with "file:" (for text files)
    or "bfile:" (for binary files).
    """

    signature_hash = _hash(signature)
    filename = os.path.join(LRU_CACHE, signature_hash)
    if not os.path.exists(LRU_CACHE):
        os.makedirs(LRU_CACHE)

    if isinstance(value, bytes):
        mode = "wb"
        signature_hash = "bfile:%s" % signature_hash
    else:
        mode = "w"
        signature_hash = "file:%s" % signature_hash

    with open(filename, mode) as f_cache:
        f_cache.write(value)
    return signature_hash

def _read_from_file(signature, sighash=None):
    """Read value for `signature` from cache file,
    or return None if file is not found.
    If `sighash` is specified, do not calculate file name
    from signature, but use `sighash` instead.

    `sigash` can be prefixed with "file:" (for text files)
    or "bfile:" (for binary files).
    """

    mode = "r"
    if sighash:
        if sighash.startswith("file:"):
            sighash = sighash[5:]
        elif sighash.startswith("bfile:"):
            sighash = sighash[6:]
            mode = "rb"
    else:
        sighash = _hash(signature)

    filename = os.path.join(LRU_CACHE, sighash)
    if not os.path.exists(filename):
        return None

    with open(filename, mode) as f_cache:
        return f_cache.read()