from datetime import timedelta, datetime
import lcoreapi
from django.conf import settings
import logging

cluster_messages = settings.LAMBDAINST_CLUSTER_MESSAGES

lcore_settings = settings.LCORE

LCORE_BASE_URL = lcore_settings.get('BASE_URL')
LCORE_API_KEY = lcore_settings['API_KEY']
LCORE_API_SECRET = lcore_settings['API_SECRET']
LCORE_SOURCE_ADDR = lcore_settings.get('SOURCE_ADDRESS')
LCORE_INST_SECRET = lcore_settings['INST_SECRET']
LCORE_TIMEOUT = lcore_settings.get('TIMEOUT', 10)

# The default is to log the exception and only raise it if we cannot show
# the previous value or a default value instead.
LCORE_RAISE_ERRORS = bool(lcore_settings.get('RAISE_ERRORS', False))

LCORE_CACHE_TTL = lcore_settings.get('CACHE_TTL', 60)
if isinstance(LCORE_CACHE_TTL, int):
    LCORE_CACHE_TTL = timedelta(seconds=LCORE_CACHE_TTL)
assert isinstance(LCORE_CACHE_TTL, timedelta)

VPN_AUTH_STORAGE = settings.VPN_AUTH_STORAGE
assert VPN_AUTH_STORAGE in ('core', 'inst')

core_api = lcoreapi.API(LCORE_API_KEY, LCORE_API_SECRET, LCORE_BASE_URL,
                        timeout=LCORE_TIMEOUT)


class APICache:
    """ Cache data for a time, try to update and silence errors.
    Outdated data is not a problem.
    """
    def __init__(self, ttl=None, initial=None):
        self.cache_date = datetime.fromtimestamp(0)
        self.ttl = ttl or LCORE_CACHE_TTL

        self.has_cached_value = initial is not None
        self.cached = initial() if initial else None

    def query(self, wrapped, *args, **kwargs):
        try:
            return wrapped(*args, **kwargs)
        except lcoreapi.APIError:
            logger = logging.getLogger('django.request')
            logger.exception("core api error")

            if LCORE_RAISE_ERRORS:
                raise

            if not self.has_cached_value:
                # We only return a default value if we were given one.
                # Prevents returning an unexpected None.
                raise

            # Return previous value
            return self.cached

    def __call__(self, wrapped):
        def wrapper(*args, **kwargs):
            if self.cache_date > (datetime.now() - self.ttl):
                return self.cached

            self.cached = self.query(wrapped, *args, **kwargs)

            # New results *and* errors are cached
            self.cache_date = datetime.now()
            return self.cached
        return wrapper


@APICache(initial=lambda: 0)
def current_active_sessions():
    return core_api.get(core_api.info['current_instance'] + '/sessions', active=True)['total_count']


@APICache(initial=lambda: [])
def get_locations():
    gateways = core_api.get('gateways/', enabled=True)
    locations = {}

    for gw in gateways.list_iter():
        cc = gw['cluster_name']

        if cc not in locations:
            locations[cc] = dict(
                servers=0,
                bandwidth=0,
                hostname='gw.' + cc + '.204vpn.net',
                country_code=cc,
                message=cluster_messages.get(cc),
            )

        locations[cc]['servers'] += 1
        locations[cc]['bandwidth'] += gw['bandwidth'] or 0

    locations = sorted(locations.items(), key=lambda x: x[1]['country_code'])
    return locations


@APICache(initial=lambda: [])
def get_gateway_exit_ips():
    gateways = core_api.get('gateways/', enabled=True)
    ipv4_list = []
    ipv6_list = []

    for gw in gateways.list_iter():
        ma = gw['main_addr']
        if ma.get('ipv4'):
            ipv4_list.append(ma['ipv4'])
        if ma.get('ipv6'):
            ipv6_list.append(ma['ipv6'])

    # TODO: IPv6 support

    return ipv4_list


def is_vpn_gateway(ip):
    addresses = get_gateway_exit_ips()
    return ip in addresses


def create_user(username, cleartext_password):
    """ The password will be hashed and stored safely on the core,
    so we have to send it clearly here.
    """
    path = core_api.info['current_instance'] + '/users/'
    core_api.post(path, data={
        'username': username,
        'password': cleartext_password,
        'expiration_date': datetime(1, 1, 1).isoformat(),  # Expired.
    })


def update_user_expiration(user):
    path = core_api.info['current_instance'] + '/users/' + user.username

    try:
        if not user.is_active:
            core_api.patch(path, data={
                'expiration_date': datetime(1, 1, 1).isoformat(),  # Expired.
            })
            return

        core_api.patch(path, data={
            'expiration_date': user.vpnuser.expiration,
        })
    except lcoreapi.APIError:
        # User can't do anything to this, we should just report it
        logger = logging.getLogger('django.request')
        logger.exception("core api error, missing user (exp update)")


def update_user_password(user, cleartext_password):
    path = core_api.info['current_instance'] + '/users/' + user.username

    try:
        core_api.patch(path, data={
            'password': cleartext_password,
        })
    except lcoreapi.APINotFoundError:
        # This time we can try fix it!
        create_user(user.username, cleartext_password)
    except lcoreapi.APIError:
        # and maybe fail.
        logger = logging.getLogger('django.request')
        logger.exception("core api error (password update)")


def delete_user(username):
    path = core_api.info['current_instance'] + '/users/' + username
    core_api.delete(path)

