"""Genshin wish models."""

import enum
import json
import typing
import unicodedata

if typing.TYPE_CHECKING:
    import pydantic.v1 as pydantic
else:
    try:
        import pydantic.v1 as pydantic
    except ImportError:
        import pydantic

from genshin.models.model import Aliased, APIModel, Unique

__all__ = [
    "ArtifactPreview",
    "CharacterPreview",
    "EnemyPreview",
    "WeaponPreview",
    "WikiPage",
    "WikiPageType",
]


class WikiPageType(enum.IntEnum):
    """Wiki page types."""

    UNKNOWN = 0
    CHARACTER = 2
    WEAPON = 4
    ARTIFACT = 5
    ENEMY = 7


class BaseWikiPreview(APIModel, Unique):
    """Base wiki preview."""

    id: int = Aliased("entry_page_id")
    icon: str = Aliased("icon_url")
    name: str

    @pydantic.root_validator(pre=True)
    def __unpack_filter_values(cls, values: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]:
        filter_values = {
            key.split("_", 1)[1]: value["values"][0]
            for key, value in values.get("filter_values", {}).items()
            if value["values"]
        }
        values.update(filter_values)
        return values

    @pydantic.root_validator(pre=True)
    def __flatten_display_field(cls, values: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]:
        values.update(values.get("display_field", {}))
        return values


# shuffle validators around because of nesting
BaseWikiPreview.__pre_root_validators__.reverse()


class CharacterPreview(BaseWikiPreview):
    """Character wiki preview."""

    bonus_attribute: str = Aliased("", "property")
    rarity: int
    region: str = ""
    element: str = Aliased("vision", "")
    weapon: str

    @pydantic.validator("rarity", pre=True)
    def __extract_rarity(cls, value: typing.Union[int, str]) -> int:
        if not isinstance(value, str):
            return value

        if value[0].isdigit():
            return int(value[0])

        return int(unicodedata.numeric(value[0]))


class WeaponPreview(BaseWikiPreview):
    """Weapon wiki preview."""

    bonus_attribute: str = Aliased("property")
    rarity: int
    type: str

    @pydantic.validator("rarity", pre=True)
    def __extract_rarity(cls, value: typing.Union[int, str]) -> int:
        if not isinstance(value, str):
            return value

        if value[0].isdigit():
            return int(value[0])

        return int(unicodedata.numeric(value[0]))


class ArtifactPreview(BaseWikiPreview):
    """Artifact wiki preview."""

    effect: str

    circlet_icon: str = Aliased("circlet_of_logos_icon_url")
    flower_icon: str = Aliased("flower_of_life_icon_url")
    goblet_icon: str = Aliased("goblet_of_eonothem_icon_url")
    plume_icon: str = Aliased("plume_of_death_icon_url")
    sands_icon: str = Aliased("sands_of_eon_icon_url")

    effects: typing.Mapping[int, str]

    @pydantic.root_validator(pre=True)
    def __group_effects(cls, values: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]:
        effects = {
            1: values["single_set_effect"],
            2: values["two_set_effect"],
            4: values["four_set_effect"],
        }
        values["effects"] = {amount: effect for amount, effect in effects.items() if effect}
        return values


class EnemyPreview(BaseWikiPreview):
    """Enemy wiki preview."""

    drop_materials: typing.Sequence[str]

    @pydantic.validator("drop_materials", pre=True)
    def __parse_drop_materials(cls, value: typing.Union[str, typing.Sequence[str]]) -> typing.Sequence[str]:
        return json.loads(value) if isinstance(value, str) else value


_ENTRY_PAGE_MODELS: typing.Mapping[WikiPageType, typing.Type[BaseWikiPreview]] = {
    WikiPageType.CHARACTER: CharacterPreview,
    WikiPageType.WEAPON: WeaponPreview,
    WikiPageType.ARTIFACT: ArtifactPreview,
    WikiPageType.ENEMY: EnemyPreview,
}


class WikiPage(APIModel):
    """Wiki page."""

    id: int
    page_type: WikiPageType = Aliased("menu_id")

    description: str = Aliased("desc")
    header: str = Aliased("header_img_url")
    icon: str = Aliased("icon_url")

    modules: typing.Mapping[str, typing.Mapping[str, typing.Any]]

    @pydantic.validator("modules", pre=True)
    def __format_modules(
        cls,
        value: typing.Union[typing.List[typing.Dict[str, typing.Any]], typing.Dict[str, typing.Any]],
    ) -> typing.Dict[str, typing.Any]:
        if isinstance(value, typing.Mapping):
            return value

        modules: typing.Dict[str, typing.Dict[str, typing.Any]] = {}
        for module in value:
            components: typing.Dict[str, typing.Dict[str, typing.Any]] = {
                component["component_id"]: json.loads(component["data"] or "{}") for component in module["components"]
            }

            components.pop("map", None)  # not worth storing
            if "reliquary_set_effect" in components:
                # Attributes of artifacts should be flattened
                components["baseInfo"]["reliquary_set_effect"] = components.pop("reliquary_set_effect")

            _, modules[module["name"]] = components.popitem()
            continue

        return modules
