import logging
import os
import pathlib
import pickle
from typing import Callable

logger = logging.getLogger()


class Cache:
    def __init__(
        self,
        name: str,
        func: Callable,
        hash_func: Callable,
        folder: str,
        serializer: Callable,
        deserializer: Callable,
    ):
        self._name = name
        self._func = func
        self._hash_func = hash_func
        self._folder = folder
        self._serializer = serializer
        self._deserializer = deserializer

    async def __aenter__(self):
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        pass

    async def __call__(self, *args, **kwargs):
        hash_sum = self._hash_func(*args, **kwargs)
        if hash_sum is None:
            return await self._func(*args, **kwargs)

        cache_file = pathlib.Path(self._folder, f"{self._name}-{hash_sum}.cache")
        if cache_file.exists():
            try:
                with cache_file.open("rb") as inp:
                    return self._deserializer(inp.read())
            except Exception:
                logger.exception(f"Can't read cache for {self._name}")
                if cache_file.exists():
                    os.remove(cache_file)

        data = await self._func(*args, **kwargs)
        if data is not None:
            try:
                with cache_file.open("wb") as out:
                    out.write(self._serializer(data))
            except Exception:
                logger.exception(f"Can't write cache for {self._name}")
                if cache_file.exists():
                    os.remove(cache_file)

        return data


def pickle_cache(name: str, func: Callable, hash_func: Callable, folder: str) -> Cache:
    return Cache(
        name=name,
        func=func,
        hash_func=hash_func,
        folder=folder,
        serializer=pickle.dumps,
        deserializer=pickle.loads,
    )
