# By: Riasat Ullah
# This file contains queries to handle services being stored in cache.

from objects.service import Service
from utils import cache_names, helpers
import json


def store_service(client, service_obj):
    '''
    Stores a service in cache.
    :param client: cache client
    :param service_obj: Service object
    '''
    assert isinstance(service_obj, Service)
    client.hset(cache_names.services, service_obj.service_id, json.dumps(service_obj.to_dict(),
                                                                         default=helpers.jsonify_unserializable))
    client.hset(cache_names.services_ref_map, str(service_obj.service_ref_id), str(service_obj.service_id))


def store_multiple_services(client, serv_obj_dict):
    '''
    Stores multiple services in cache.
    :param client: cache client
    :param serv_obj_dict: (dict) -> {serv ID: Service, ...}
    '''
    assert isinstance(serv_obj_dict, dict) and len(serv_obj_dict) > 0
    serv_data = dict()
    ref_data = dict()

    for id_ in serv_obj_dict:
        serv = serv_obj_dict[id_]
        assert isinstance(serv, Service)
        serv_data[id_] = json.dumps(serv.to_dict(), default=helpers.jsonify_unserializable)
        ref_data[str(serv.service_ref_id)] = id_

    client.hmset(cache_names.services, serv_data)
    client.hmset(cache_names.services_ref_map, ref_data)


def get_single_service(client, serv_id=None, serv_ref_id=None):
    '''
    Get a single service object.
    :param client: cache client
    :param serv_id: service ID
    :param serv_ref_id: (unmasked) service reference ID
    :return: Service object
    '''
    assert serv_id is not None or serv_ref_id is not None
    if serv_ref_id is not None:
        serv_id = client.hget(cache_names.services_ref_map, str(serv_ref_id))

    if serv_id is not None:
        json_serv = client.hget(cache_names.services, serv_id)
        if json_serv is not None:
            return Service.create_service(json.loads(json_serv))
    return None


def get_multiple_services(client, service_ids):
    '''
    Get multiple services at the same time.
    :param client: cache client
    :param service_ids: (list of int) service IDs
    :return: (dict) -> {ID: Service, ...}
    '''
    data = dict()
    serv_json = client.hmget(cache_names.services, service_ids)
    if serv_json is not None:
        for item in serv_json:
            if item is not None:
                serv = Service.create_service(json.loads(item))
                data[serv.service_id] = serv
    return data


def get_all_services_ref_map(client):
    '''
    Gets all service reference maps that are stored in cache.
    :param client: cache client
    :return: (dict) -> {ref ID: service ID, ...}
    '''
    ref_map = client.hgetall(cache_names.services_ref_map)
    if ref_map is not None:
        for ref_id in ref_map:
            ref_map[ref_id] = int(ref_map[ref_id])
        return ref_map
    else:
        return dict()


def remove_services(client, service_ids):
    '''
    Remove services from cache.
    :param client: cache client
    :param service_ids: (list) IDs of the services to remove
    '''
    service_ids = helpers.get_int_list(service_ids)
    if len(service_ids) > 0:
        client.hdel(cache_names.services, *service_ids)

        cached_refs = get_all_services_ref_map(client)
        refs_to_remove = []
        for ref_id in cached_refs:
            if cached_refs[ref_id] in service_ids:
                refs_to_remove.append(ref_id)
        if len(refs_to_remove) > 0:
            remove_services_ref_map(client, refs_to_remove)


def remove_services_ref_map(client, service_ref_ids):
    '''
    Removes service reference ID maps from the cache.
    :param client: cache client
    :param service_ref_ids: (list of str) of service reference IDs
    '''
    if len(service_ref_ids) > 0:
        client.hdel(cache_names.services_ref_map, *service_ref_ids)


def remove_all_services(client):
    '''
    Removes all services and their reference maps from cache.
    :param client: cache client
    '''
    client.delete(cache_names.services)
    client.delete(cache_names.services_ref_map)


def is_service_in_cache(client, service_id):
    '''
    Checks if a certain policy is in cache or not.
    :param client: cache client
    :param service_id: the service ID to check for
    :return: (boolean) True if it is in cache; False otherwise
    '''
    assert isinstance(service_id, int)
    return client.hexists(cache_names.services, service_id)


def get_cached_service_ids(client):
    '''
    Get the list of IDs of the services that have been cached.
    :param client: cache client
    :return: (list) of service IDs
    '''
    serv_ids = client.hkeys(cache_names.services)
    return [int(x) for x in serv_ids] if serv_ids is not None else []
