import time
from typing import Optional, Dict

import safe_radix32
import ttldict2

from clarabot_nano.common.crypto.keyring import NanoSelfAccountKeyring, NanoPeerAccountKeyring, NanoRoomKeyring


class KeyringCache(object):
    """
    The used keys in an account-keyring are designed for slow-upgrade or change. New key(s) will be introduced and
    only after an extensive time will they be used. This allows the clients to lazily update account keyrings and
    cache them for a relatively long time.

    The self-keyring of an account is special, it is always available even it is considered "expired", which means
    it should be updated.

    NOTE: This is made to be primarily useful for Nano and not the clients.
    """

    __slots__ = ("self_kr", "_self_ttl", "_peer_krs", "_anon_krs", "_own_room_krs")

    CACHE_TTL = float(60 * 60 * 24 * 7)  # 1 week

    def __init__(self, self_kr: NanoSelfAccountKeyring):
        self.self_kr = self_kr
        self._self_ttl = time.perf_counter() + self.CACHE_TTL
        self._peer_krs = ttldict2.TTLDict(self.CACHE_TTL, max_items=4000)
        self._own_room_krs = ttldict2.TTLDict(self.CACHE_TTL, max_items=1000)

    def is_self_kr_expired(self) -> bool:
        """
        Updating the self keyring will be necessary in the future when new keys will be introduced. For now there
        is literally nothing to be done because new keys cannot be loaded by a client that does not support them
        and all currently supported keys are mandatory for operation.
        """
        return self._self_ttl < time.perf_counter()

    def get_peer_kr(self, account_id: int) -> Optional[NanoPeerAccountKeyring]:
        return self._peer_krs.get(account_id)

    def load_peer_kr(
        self,
        account_id: int,
        curve25519_public: bytes,
        curve25519_public_sig: bytes,
        ed25519_public: bytes,
        trust_fingerprint: Optional[bytes] = None,
        stored_tofu_fingerprints: Optional[Dict[str, bytes]] = None,
    ) -> NanoPeerAccountKeyring:
        """
        Similarly to the self keyring, the peer account keyrings are constant for new, because there is only one
        version of required keys.
        """
        peer_kr = NanoPeerAccountKeyring.load(
            safe_radix32.encode_fw(account_id),
            curve25519_public,
            curve25519_public_sig,
            ed25519_public,
            trust_fingerprint=trust_fingerprint,
            self_kr=self.self_kr,
            stored_tofu_fingerprints=stored_tofu_fingerprints,
        )
        self._peer_krs[account_id] = peer_kr
        return peer_kr

    def get_own_room_kr(self, room_id: int) -> Optional[NanoRoomKeyring]:
        return self._own_room_krs.get(room_id)

    def load_own_room_kr(self, room_id: int, pinned_key: bytes):
        room_kr = NanoRoomKeyring.load(self.self_kr, self.self_kr, safe_radix32.encode_fw(room_id), None, pinned_key)
        self._own_room_krs[room_id] = room_kr
        return room_kr
