
from __future__ import annotations

from typing import Optional, Tuple, Sequence, List

from aioredis import Redis, ReplyError

from .keys import NamespaceKeys, FilterKeys
from .scripts.filter import FilterScripts
from pymap.filter import PluginFilterSet
from pymap.parsing.specials import ObjectId

__all__ = ['FilterSet']

_scripts = FilterScripts()


class FilterSet(PluginFilterSet[bytes]):

    _active_name = b''

    def __init__(self, redis: Redis, ns_keys: NamespaceKeys) -> None:
        super().__init__('sieve', bytes)
        self._redis = redis
        self._keys = FilterKeys(ns_keys)

    async def put(self, name: str, value: bytes) -> None:
        keys = self._keys
        name_bytes = name.encode('utf-8')
        key = ObjectId.random(b'S').value
        pipe = self._redis.pipeline()
        pipe.hset(keys.data, key, value)
        pipe.hset(keys.names, name_bytes, key)
        await pipe.execute()

    async def delete(self, name: str) -> None:
        try:
            await _scripts.delete(self._redis, self._keys,
                                  name=name.encode('utf-8'),
                                  active_name=self._active_name)
        except ReplyError as exc:
            if 'filter not found' in str(exc):
                raise KeyError(name) from exc
            elif 'filter is active' in str(exc):
                raise ValueError(name) from exc
            raise

    async def rename(self, before_name: str, after_name: str) -> None:
        try:
            await _scripts.rename(self._redis, self._keys,
                                  before_name=before_name.encode('utf-8'),
                                  after_name=after_name.encode('utf-8'))
        except ReplyError as exc:
            if 'filter not found' in str(exc):
                raise KeyError(before_name) from exc
            elif 'filter already exists' in str(exc):
                raise KeyError(after_name) from exc
            raise

    async def clear_active(self) -> None:
        keys = self._keys
        await self._redis.hdel(keys.names, self._active_name)

    async def set_active(self, name: str) -> None:
        try:
            await _scripts.set_active(self._redis, self._keys,
                                      name=name.encode('utf-8'),
                                      active_name=self._active_name)
        except ReplyError as exc:
            if 'filter not found' in str(exc):
                raise KeyError(name) from exc
            raise

    async def get(self, name: str) -> bytes:
        try:
            return await _scripts.get(self._redis, self._keys,
                                      name=name.encode('utf-8'))
        except ReplyError as exc:
            if 'filter not found' in str(exc):
                raise KeyError(name) from exc
            raise

    async def get_active(self) -> Optional[bytes]:
        try:
            return await _scripts.get(self._redis, self._keys,
                                      name=self._active_name)
        except ReplyError as exc:
            if 'filter not found' in str(exc):
                return None
            raise

    async def get_all(self) -> Tuple[Optional[str], Sequence[str]]:
        keys = self._keys
        sieve_names = await self._redis.hgetall(keys.names)
        active_key: Optional[bytes] = sieve_names.get(self._active_name)
        active_name: Optional[str] = None
        names: List[str] = []
        for name, key in sieve_names.items():
            if name != self._active_name:
                name_str = name.decode('utf-8')
                names.append(name_str)
                if key == active_key:
                    active_name = name_str
        return active_name, names
