"""Habitica button platform."""

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass
from enum import StrEnum
from typing import Any

from habiticalib import Habitica, HabiticaClass, Skill, TaskType

from homeassistant.components.button import (
    DOMAIN as BUTTON_DOMAIN,
    ButtonEntity,
    ButtonEntityDescription,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .const import ASSETS_URL, DOMAIN
from .coordinator import HabiticaConfigEntry, HabiticaData
from .entity import HabiticaBase

PARALLEL_UPDATES = 1


@dataclass(kw_only=True, frozen=True)
class HabiticaButtonEntityDescription(ButtonEntityDescription):
    """Describes Habitica button entity."""

    press_fn: Callable[[Habitica], Any]
    available_fn: Callable[[HabiticaData], bool]
    class_needed: HabiticaClass | None = None
    entity_picture: str | None = None


class HabiticaButtonEntity(StrEnum):
    """Habitica button entities."""

    RUN_CRON = "run_cron"
    BUY_HEALTH_POTION = "buy_health_potion"
    ALLOCATE_ALL_STAT_POINTS = "allocate_all_stat_points"
    REVIVE = "revive"
    MPHEAL = "mpheal"
    EARTH = "earth"
    FROST = "frost"
    DEFENSIVE_STANCE = "defensive_stance"
    VALOROUS_PRESENCE = "valorous_presence"
    INTIMIDATE = "intimidate"
    TOOLS_OF_TRADE = "tools_of_trade"
    STEALTH = "stealth"
    HEAL = "heal"
    PROTECT_AURA = "protect_aura"
    BRIGHTNESS = "brightness"
    HEAL_ALL = "heal_all"


BUTTON_DESCRIPTIONS: tuple[HabiticaButtonEntityDescription, ...] = (
    HabiticaButtonEntityDescription(
        key=HabiticaButtonEntity.RUN_CRON,
        translation_key=HabiticaButtonEntity.RUN_CRON,
        press_fn=lambda habitica: habitica.run_cron(),
        available_fn=lambda data: data.user.needsCron is True,
    ),
    HabiticaButtonEntityDescription(
        key=HabiticaButtonEntity.BUY_HEALTH_POTION,
        translation_key=HabiticaButtonEntity.BUY_HEALTH_POTION,
        press_fn=lambda habitica: habitica.buy_health_potion(),
        available_fn=(
            lambda data: (
                (data.user.stats.gp or 0) >= 25 and (data.user.stats.hp or 0) < 50
            )
        ),
        entity_picture="shop_potion.png",
    ),
    HabiticaButtonEntityDescription(
        key=HabiticaButtonEntity.ALLOCATE_ALL_STAT_POINTS,
        translation_key=HabiticaButtonEntity.ALLOCATE_ALL_STAT_POINTS,
        press_fn=lambda habitica: habitica.allocate_stat_points(),
        available_fn=(
            lambda data: (
                data.user.preferences.automaticAllocation is True
                and (data.user.stats.points or 0) > 0
            )
        ),
    ),
    HabiticaButtonEntityDescription(
        key=HabiticaButtonEntity.REVIVE,
        translation_key=HabiticaButtonEntity.REVIVE,
        press_fn=lambda habitica: habitica.revive(),
        available_fn=lambda data: data.user.stats.hp == 0,
    ),
)


CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
    HabiticaButtonEntityDescription(
        key=HabiticaButtonEntity.MPHEAL,
        translation_key=HabiticaButtonEntity.MPHEAL,
        press_fn=lambda habitica: habitica.cast_skill(Skill.ETHEREAL_SURGE),
        available_fn=(
            lambda data: (
                (data.user.stats.lvl or 0) >= 12 and (data.user.stats.mp or 0) >= 30
            )
        ),
        class_needed=HabiticaClass.MAGE,
        entity_picture="shop_mpheal.png",
    ),
    HabiticaButtonEntityDescription(
        key=HabiticaButtonEntity.EARTH,
        translation_key=HabiticaButtonEntity.EARTH,
        press_fn=lambda habitica: habitica.cast_skill(Skill.EARTHQUAKE),
        available_fn=(
            lambda data: (
                (data.user.stats.lvl or 0) >= 13 and (data.user.stats.mp or 0) >= 35
            )
        ),
        class_needed=HabiticaClass.MAGE,
        entity_picture="shop_earth.png",
    ),
    HabiticaButtonEntityDescription(
        key=HabiticaButtonEntity.FROST,
        translation_key=HabiticaButtonEntity.FROST,
        press_fn=lambda habitica: habitica.cast_skill(Skill.CHILLING_FROST),
        # chilling frost can only be cast once per day (streaks buff is false)
        available_fn=(
            lambda data: (
                (data.user.stats.lvl or 0) >= 14
                and (data.user.stats.mp or 0) >= 40
                and not data.user.stats.buffs.streaks
            )
        ),
        class_needed=HabiticaClass.MAGE,
        entity_picture="shop_frost.png",
    ),
    HabiticaButtonEntityDescription(
        key=HabiticaButtonEntity.DEFENSIVE_STANCE,
        translation_key=HabiticaButtonEntity.DEFENSIVE_STANCE,
        press_fn=lambda habitica: habitica.cast_skill(Skill.DEFENSIVE_STANCE),
        available_fn=(
            lambda data: (
                (data.user.stats.lvl or 0) >= 12 and (data.user.stats.mp or 0) >= 25
            )
        ),
        class_needed=HabiticaClass.WARRIOR,
        entity_picture="shop_defensiveStance.png",
    ),
    HabiticaButtonEntityDescription(
        key=HabiticaButtonEntity.VALOROUS_PRESENCE,
        translation_key=HabiticaButtonEntity.VALOROUS_PRESENCE,
        press_fn=lambda habitica: habitica.cast_skill(Skill.VALOROUS_PRESENCE),
        available_fn=(
            lambda data: (
                (data.user.stats.lvl or 0) >= 13 and (data.user.stats.mp or 0) >= 20
            )
        ),
        class_needed=HabiticaClass.WARRIOR,
        entity_picture="shop_valorousPresence.png",
    ),
    HabiticaButtonEntityDescription(
        key=HabiticaButtonEntity.INTIMIDATE,
        translation_key=HabiticaButtonEntity.INTIMIDATE,
        press_fn=lambda habitica: habitica.cast_skill(Skill.INTIMIDATING_GAZE),
        available_fn=(
            lambda data: (
                (data.user.stats.lvl or 0) >= 14 and (data.user.stats.mp or 0) >= 15
            )
        ),
        class_needed=HabiticaClass.WARRIOR,
        entity_picture="shop_intimidate.png",
    ),
    HabiticaButtonEntityDescription(
        key=HabiticaButtonEntity.TOOLS_OF_TRADE,
        translation_key=HabiticaButtonEntity.TOOLS_OF_TRADE,
        press_fn=lambda habitica: habitica.cast_skill(Skill.TOOLS_OF_THE_TRADE),
        available_fn=(
            lambda data: (
                (data.user.stats.lvl or 0) >= 13 and (data.user.stats.mp or 0) >= 25
            )
        ),
        class_needed=HabiticaClass.ROGUE,
        entity_picture="shop_toolsOfTrade.png",
    ),
    HabiticaButtonEntityDescription(
        key=HabiticaButtonEntity.STEALTH,
        translation_key=HabiticaButtonEntity.STEALTH,
        press_fn=lambda habitica: habitica.cast_skill(Skill.STEALTH),
        # Stealth buffs stack and it can only be cast if the amount of
        # buffs is smaller than the amount of unfinished dailies
        available_fn=(
            lambda data: (
                (data.user.stats.lvl or 0) >= 14
                and (data.user.stats.mp or 0) >= 45
                and (data.user.stats.buffs.stealth or 0)
                < len(
                    [
                        r
                        for r in data.tasks
                        if r.Type is TaskType.DAILY
                        and r.isDue is True
                        and r.completed is False
                    ]
                )
            )
        ),
        class_needed=HabiticaClass.ROGUE,
        entity_picture="shop_stealth.png",
    ),
    HabiticaButtonEntityDescription(
        key=HabiticaButtonEntity.HEAL,
        translation_key=HabiticaButtonEntity.HEAL,
        press_fn=lambda habitica: habitica.cast_skill(Skill.HEALING_LIGHT),
        available_fn=(
            lambda data: (
                (data.user.stats.lvl or 0) >= 11
                and (data.user.stats.mp or 0) >= 15
                and (data.user.stats.hp or 0) < 50
            )
        ),
        class_needed=HabiticaClass.HEALER,
        entity_picture="shop_heal.png",
    ),
    HabiticaButtonEntityDescription(
        key=HabiticaButtonEntity.BRIGHTNESS,
        translation_key=HabiticaButtonEntity.BRIGHTNESS,
        press_fn=lambda habitica: habitica.cast_skill(Skill.SEARING_BRIGHTNESS),
        available_fn=(
            lambda data: (
                (data.user.stats.lvl or 0) >= 12 and (data.user.stats.mp or 0) >= 15
            )
        ),
        class_needed=HabiticaClass.HEALER,
        entity_picture="shop_brightness.png",
    ),
    HabiticaButtonEntityDescription(
        key=HabiticaButtonEntity.PROTECT_AURA,
        translation_key=HabiticaButtonEntity.PROTECT_AURA,
        press_fn=lambda habitica: habitica.cast_skill(Skill.PROTECTIVE_AURA),
        available_fn=(
            lambda data: (
                (data.user.stats.lvl or 0) >= 13 and (data.user.stats.mp or 0) >= 30
            )
        ),
        class_needed=HabiticaClass.HEALER,
        entity_picture="shop_protectAura.png",
    ),
    HabiticaButtonEntityDescription(
        key=HabiticaButtonEntity.HEAL_ALL,
        translation_key=HabiticaButtonEntity.HEAL_ALL,
        press_fn=lambda habitica: habitica.cast_skill(Skill.BLESSING),
        available_fn=(
            lambda data: (
                (data.user.stats.lvl or 0) >= 14 and (data.user.stats.mp or 0) >= 25
            )
        ),
        class_needed=HabiticaClass.HEALER,
        entity_picture="shop_healAll.png",
    ),
)


async def async_setup_entry(
    hass: HomeAssistant,
    entry: HabiticaConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up buttons from a config entry."""

    coordinator = entry.runtime_data
    skills_added: set[str] = set()

    @callback
    def add_entities() -> None:
        """Add or remove a skillset based on the player's class."""

        nonlocal skills_added
        buttons = []
        entity_registry = er.async_get(hass)

        for description in CLASS_SKILLS:
            if (
                (coordinator.data.user.stats.lvl or 0) >= 10
                and coordinator.data.user.flags.classSelected
                and not coordinator.data.user.preferences.disableClasses
                and description.class_needed is coordinator.data.user.stats.Class
            ):
                if description.key not in skills_added:
                    buttons.append(HabiticaButton(coordinator, description))
                    skills_added.add(description.key)
            elif description.key in skills_added:
                if entity_id := entity_registry.async_get_entity_id(
                    BUTTON_DOMAIN,
                    DOMAIN,
                    f"{coordinator.config_entry.unique_id}_{description.key}",
                ):
                    entity_registry.async_remove(entity_id)
                skills_added.remove(description.key)

        if buttons:
            async_add_entities(buttons)

    coordinator.async_add_listener(add_entities)
    add_entities()

    async_add_entities(
        HabiticaButton(coordinator, description) for description in BUTTON_DESCRIPTIONS
    )


class HabiticaButton(HabiticaBase, ButtonEntity):
    """Representation of a Habitica button."""

    entity_description: HabiticaButtonEntityDescription

    async def async_press(self) -> None:
        """Handle the button press."""

        await self.coordinator.execute(self.entity_description.press_fn)
        await self.coordinator.async_request_refresh()

    @property
    def available(self) -> bool:
        """Is entity available."""

        return super().available and self.entity_description.available_fn(
            self.coordinator.data
        )

    @property
    def entity_picture(self) -> str | None:
        """Return the entity picture to use in the frontend, if any."""
        if entity_picture := self.entity_description.entity_picture:
            return f"{ASSETS_URL}{entity_picture}"
        return None
