import json
import typing as t
from collections.abc import MutableMapping

import aiofiles

if t.TYPE_CHECKING:
    from pokelance import models  # noqa: F401
    from pokelance.http import Route  # noqa: F401


__all__: t.Tuple[str, ...] = (
    "PokemonCache",
    "BaseCache",
    "BerryCache",
    "PalParkAreaCache",
    "GenderCache",
    "GamesPokedexCache",
    "GamesVersionCache",
    "GamesGenerationCache",
    "BerryFlavorCache",
    "BerryFirmnessCache",
    "ContestTypeCache",
    "PokeathlonStatCache",
    "PokemonSpeciesCache",
    "PokemonLocationAreaCache",
    "ContestEffectCache",
    "SuperContestEffectCache",
    "PokemonColorCache",
    "PokemonFormCache",
    "PokemonHabitatCache",
    "PokemonShapeCache",
    "GrowthRateCache",
    "NatureCache",
    "TypeCache",
    "GenderCache",
    "GamesVersionGroupCache",
    "EggGroupCache",
    "EvolutionChainCache",
    "EvolutionTriggerCache",
    "EncounterConditionCache",
    "EncounterConditionValueCache",
    "EncounterMethodCache",
    "ItemCache",
    "ItemAttributeCache",
    "ItemCategoryCache",
    "ItemFlingEffectCache",
    "ItemPocketCache",
    "LocationCache",
    "LocationAreaCache",
    "MachineCache",
    "MoveCache",
    "MoveAilmentCache",
    "MoveBattleStyleCache",
    "MoveCategoryCache",
    "MoveDamageClassCache",
    "MoveLearnMethodCache",
    "MoveTargetCache",
    "AbilityCache",
    "CharacteristicCache",
    "PokemonCache",
    "PokemonSpeciesCache",
    "PokemonFormCache",
    "NatureCache",
    "TypeCache",
    "GenderCache",
    "GrowthRateCache",
    "PokemonLocationAreaCache",
    "StatCache",
    "RegionCache",
)

_KT = t.TypeVar("_KT")
_VT = t.TypeVar("_VT")


class BaseCache(MutableMapping[_KT, _VT]):
    """Base class for all caches.

    Parameters
    ----------
    max_size: int
        The maximum size of the cache.

    Attributes
    ----------
    _max_size: int
        The maximum size of the cache.
    _cache: typing.Dict[_KT, _VT]
        The cache itself.
    _endpoints: typing.Dict[str, int]
        The endpoints that are cached.
    """

    def __init__(self, max_size: int = 100) -> None:
        self._max_size = max_size
        self._cache: t.Dict[_KT, _VT] = {}
        self._endpoints: t.Dict[str, int] = {}

    def __getitem__(self, key: _KT) -> _VT:
        self._cache[key] = self._cache.pop(key)
        return self._cache[key]

    def __setitem__(self, key: _KT, value: _VT) -> None:
        if key in self._cache:
            self._cache[key] = self._cache.pop(key)
        else:
            if len(self._cache) >= self._max_size:
                self._cache.pop(list(self._cache.keys())[0])
            self._cache[key] = value

    def __delitem__(self, key: _KT) -> None:
        del self._cache[key]

    def __len__(self) -> int:
        return len(self._cache)

    def __iter__(self) -> t.Iterator[_KT]:
        return iter(self._cache)

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self._cache})"

    def setdefault(self, __key: _KT, __default: t.Any = ...) -> _VT:
        item = self._cache.setdefault(__key, __default)
        if item is __default:
            self._cache[__key] = self._cache.pop(__key)
        return item

    def load_documents(self, data: t.List[t.Dict[str, str]]) -> None:
        """Load documents into the cache.

        Parameters
        ----------
        data: typing.List[typing.Dict[str, str]]
            The data to load.
        """
        for document in data:
            self._endpoints[document["name"]] = int(document["url"].split("/")[-2])

    async def save(self, path: str = "") -> None:
        """Save the cache to a file.

        Parameters
        ----------
        path: str
            The path to save the cache to.
        """
        async with aiofiles.open(f"{path}{self.__class__.__name__}.json", "w") as f:
            await f.write(json.dumps(self._cache, indent=4, ensure_ascii=False))

    @property
    def endpoints(self) -> t.Dict[str, int]:
        """The endpoints that are cached.

        Returns
        -------
        typing.Dict[str, int]
            The endpoints that are cached.
        """
        return self._endpoints

    @property
    def cache(self) -> t.Dict[_KT, _VT]:
        """The cache itself.

        Returns
        -------
        typing.Dict[_KT, _VT]
            The cache itself.
        """
        return self._cache


class SecondaryTypeCache(BaseCache[_KT, _VT]):
    """A cache for secondary types with differing endpoints."""

    def load_documents(self, data: t.List[t.Dict[str, str]]) -> None:
        """Load documents into the cache. Endpoints are different for secondary types.

        Parameters
        ----------
        data: typing.List[typing.Dict[str, str]]
            The data to load.
        """
        for document in data:
            self._endpoints[document["url"].split("/")[-2]] = int(document["url"].split("/")[-2])


class BerryCache(BaseCache["Route", "models.Berry"]):
    """A cache for berries."""


class BerryFirmnessCache(BaseCache["Route", "models.BerryFirmness"]):
    """A cache for berry firmnesses."""


class BerryFlavorCache(BaseCache["Route", "models.BerryFlavor"]):
    """A cache for berry flavors."""


class ContestTypeCache(BaseCache["Route", "models.ContestType"]):
    """A cache for contest types."""


class PokemonCache(BaseCache["Route", "models.Pokemon"]):
    """A cache for pokemon."""


class AbilityCache(BaseCache["Route", "models.Ability"]):
    """A cache for abilities."""


class EggGroupCache(BaseCache["Route", "models.EggGroup"]):
    """A cache for egg groups."""


class GenderCache(BaseCache["Route", "models.Gender"]):
    """A cache for genders."""


class GrowthRateCache(BaseCache["Route", "models.GrowthRate"]):
    """A cache for growth rates."""


class NatureCache(BaseCache["Route", "models.Nature"]):
    """A cache for natures."""


class PokeathlonStatCache(BaseCache["Route", "models.PokeathlonStat"]):
    """A cache for pokeathlon stats."""


class PokemonColorCache(BaseCache["Route", "models.PokemonColor"]):
    """A cache for pokemon colors."""


class PokemonFormCache(BaseCache["Route", "models.PokemonForm"]):
    """A cache for pokemon forms."""


class PokemonLocationAreaCache(BaseCache["Route", "models.LocationAreaEncounter"]):
    """A cache for pokemon location areas."""


class PokemonHabitatCache(BaseCache["Route", "models.PokemonHabitats"]):
    """A cache for pokemon habitats."""


class PokemonShapeCache(BaseCache["Route", "models.PokemonShape"]):
    """A cache for pokemon shapes."""


class PokemonSpeciesCache(BaseCache["Route", "models.PokemonSpecies"]):
    """A cache for pokemon species."""


class StatCache(BaseCache["Route", "models.Stat"]):
    """A cache for stats."""


class TypeCache(BaseCache["Route", "models.Type"]):
    """A cache for types."""


class EncounterMethodCache(BaseCache["Route", "models.EncounterMethod"]):
    """A cache for encounter methods."""


class EncounterConditionCache(BaseCache["Route", "models.EncounterCondition"]):
    """A cache for encounter conditions."""


class EncounterConditionValueCache(BaseCache["Route", "models.EncounterConditionValue"]):
    """A cache for encounter condition values."""


class EvolutionTriggerCache(BaseCache["Route", "models.EvolutionTrigger"]):
    """A cache for evolution triggers."""


class GamesGenerationCache(BaseCache["Route", "models.Generation"]):
    """A cache for games generations."""


class GamesPokedexCache(BaseCache["Route", "models.Pokedex"]):
    """A cache for games pokedexes."""


class GamesVersionCache(BaseCache["Route", "models.Version"]):
    """A cache for games versions."""


class GamesVersionGroupCache(BaseCache["Route", "models.VersionGroup"]):
    """A cache for games version groups."""


class ItemCache(BaseCache["Route", "models.Item"]):
    """A cache for items."""


class ItemAttributeCache(BaseCache["Route", "models.ItemAttribute"]):
    """A cache for item attributes."""


class ItemCategoryCache(BaseCache["Route", "models.ItemCategory"]):
    """A cache for item categories."""


class ItemFlingEffectCache(BaseCache["Route", "models.ItemFlingEffect"]):
    """A cache for item fling effects."""


class ItemPocketCache(BaseCache["Route", "models.ItemPocket"]):
    """A cache for item pockets."""


class LocationCache(BaseCache["Route", "models.Location"]):
    """A cache for locations."""


class LocationAreaCache(BaseCache["Route", "models.LocationArea"]):
    """A cache for location areas."""


class PalParkAreaCache(BaseCache["Route", "models.PalParkArea"]):
    """A cache for pal park areas."""


class RegionCache(BaseCache["Route", "models.Region"]):
    """A cache for regions."""


class MoveCache(BaseCache["Route", "models.Move"]):
    """A cache for moves."""


class MoveAilmentCache(BaseCache["Route", "models.MoveAilment"]):
    """A cache for move ailments."""


class MoveBattleStyleCache(BaseCache["Route", "models.MoveBattleStyle"]):
    """A cache for move battle styles."""


class MoveCategoryCache(BaseCache["Route", "models.MoveCategory"]):
    """A cache for move categories."""


class MoveDamageClassCache(BaseCache["Route", "models.MoveDamageClass"]):
    """A cache for move damage classes."""


class MoveLearnMethodCache(BaseCache["Route", "models.MoveLearnMethod"]):
    """A cache for move learn methods."""


class MoveTargetCache(BaseCache["Route", "models.MoveTarget"]):
    """A cache for move targets."""


class MachineCache(SecondaryTypeCache["Route", "models.Machine"]):
    """A cache for machines."""


class EvolutionChainCache(SecondaryTypeCache["Route", "models.EvolutionChain"]):
    """A cache for evolution chains."""


class CharacteristicCache(SecondaryTypeCache["Route", "models.Characteristic"]):
    """A cache for characteristics."""


class ContestEffectCache(SecondaryTypeCache["Route", "models.ContestEffect"]):
    """A cache for contest effects."""


class SuperContestEffectCache(SecondaryTypeCache["Route", "models.SuperContestEffect"]):
    """A cache for super contest effects."""
