from collections import defaultdict
from functools import partial
from threading import RLock

import cachetools
from flask import current_app
from notifications_utils.clients.redis import RequestCache
from notifications_utils.serialised_model import (
    SerialisedModel,
    SerialisedModelCollection,
)
from werkzeug.utils import cached_property

from app import db, redis_store
from app.dao.api_key_dao import get_model_api_keys
from app.dao.services_dao import dao_fetch_service_by_id

caches = defaultdict(partial(cachetools.TTLCache, maxsize=1024, ttl=2))
locks = defaultdict(RLock)
redis_cache = RequestCache(redis_store)


def memory_cache(func):
    @cachetools.cached(
        cache=caches[func.__qualname__],
        lock=locks[func.__qualname__],
        key=ignore_first_argument_cache_key,
    )
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

    return wrapper


def ignore_first_argument_cache_key(cls, *args, **kwargs):
    return cachetools.keys.hashkey(*args, **kwargs)


class SerialisedTemplate(SerialisedModel):
    ALLOWED_PROPERTIES = {
        "archived",
        "content",
        "id",
        "postage",
        "process_type",
        "reply_to_text",
        "subject",
        "template_type",
        "version",
    }

    @classmethod
    @memory_cache
    def from_id_and_service_id(cls, template_id, service_id, version=None):
        return cls(cls.get_dict(template_id, service_id, version)["data"])

    @staticmethod
    @redis_cache.set("service-{service_id}-template-{template_id}-version-{version}")
    def get_dict(template_id, service_id, version):
        from app.dao import templates_dao
        from app.schemas import template_schema

        fetched_template = templates_dao.dao_get_template_by_id_and_service_id(
            template_id=template_id,
            service_id=service_id,
            version=version,
        )

        template_dict = template_schema.dump(fetched_template)
        db.session.commit()

        return {"data": template_dict}


class SerialisedService(SerialisedModel):
    ALLOWED_PROPERTIES = {
        "id",
        "name",
        "active",
        "contact_link",
        "email_from",
        "email_message_limit",
        "letter_message_limit",
        "sms_message_limit",
        "permissions",
        "rate_limit",
        "restricted",
        "prefix_sms",
        "email_branding",
    }

    @classmethod
    @memory_cache
    def from_id(cls, service_id):
        return cls(cls.get_dict(service_id)["data"])

    @staticmethod
    @redis_cache.set("service-{service_id}")
    def get_dict(service_id):
        from app.schemas import service_schema

        service_dict = service_schema.dump(dao_fetch_service_by_id(service_id))
        db.session.commit()

        return {"data": service_dict}

    @cached_property
    def api_keys(self):
        return SerialisedAPIKeyCollection.from_service_id(self.id)

    @property
    def high_volume(self):
        return self.id in current_app.config["HIGH_VOLUME_SERVICE"]


class SerialisedAPIKey(SerialisedModel):
    ALLOWED_PROPERTIES = {
        "id",
        "secret",
        "expiry_date",
        "key_type",
    }


class SerialisedAPIKeyCollection(SerialisedModelCollection):
    model = SerialisedAPIKey

    @classmethod
    @memory_cache
    def from_service_id(cls, service_id):
        keys = [
            {k: getattr(key, k) for k in SerialisedAPIKey.ALLOWED_PROPERTIES} for key in get_model_api_keys(service_id)
        ]
        db.session.commit()
        return cls(keys)
