"""Quests (chore chains) mixin for TaskMateCoordinator.

A quest is an ordered list of chores. Each child works through the chain one
step at a time: completing (and getting approval for) the chore at their
current step advances them. Finishing the final step awards the quest's bonus
points, fires a ``taskmate_quest_completed`` event + celebration, and either
resets progress (repeatable quests) or marks the quest complete for that child.
"""

from __future__ import annotations

import logging

from homeassistant.util import dt as dt_util

from .models import PointsTransaction, Quest

_LOGGER = logging.getLogger(__name__)


class QuestsMixin:
    """Mixin providing quest CRUD and chore-chain progression."""

    # ── CRUD ─────────────────────────────────────────────────────────────
    async def async_create_quest(self, **fields) -> str:
        quest = Quest.from_dict(fields)
        if not quest.name.strip():
            raise ValueError("Quest name is required")
        if not quest.steps:
            raise ValueError("A quest needs at least one step")
        self.storage.add_quest(quest)
        await self.storage.async_save()
        await self.async_refresh()
        return quest.id

    async def async_update_quest(self, quest_id: str, **fields) -> None:
        existing = self.storage.get_quest(quest_id)
        if not existing:
            raise ValueError(f"Quest {quest_id} not found")
        data = existing.to_dict()
        data.update(fields)
        data["id"] = quest_id
        updated = Quest.from_dict(data)
        if not updated.steps:
            raise ValueError("A quest needs at least one step")
        self.storage.update_quest(updated)
        await self.storage.async_save()
        await self.async_refresh()

    async def async_delete_quest(self, quest_id: str) -> None:
        if not self.storage.get_quest(quest_id):
            raise ValueError(f"Quest {quest_id} not found")
        self.storage.remove_quest(quest_id)
        await self.storage.async_save()
        await self.async_refresh()

    # ── Progress helpers ─────────────────────────────────────────────────
    def _quest_applies_to(self, quest: Quest, child_id: str) -> bool:
        return not quest.assigned_to or child_id in quest.assigned_to

    def quest_progress_for_child(self, child_id: str) -> list[dict]:
        """Snapshot of every active quest assigned to ``child_id``.

        Returned shape (for sensors / cards):
        ``{quest_id, name, icon, total_steps, step, done, times_completed,
        bonus_points, next_chore_id}``.
        """
        out: list[dict] = []
        for quest in self.storage.get_quests():
            if not quest.active or not self._quest_applies_to(quest, child_id):
                continue
            prog = self.storage.get_quest_child_progress(quest.id, child_id)
            step = int(prog.get("step", 0))
            total = len(quest.steps)
            done = step >= total
            out.append(
                {
                    "quest_id": quest.id,
                    "name": quest.name,
                    "icon": quest.icon,
                    "total_steps": total,
                    "step": min(step, total),
                    "done": done,
                    "times_completed": int(prog.get("completed_count", 0)),
                    "bonus_points": quest.bonus_points,
                    "next_chore_id": quest.steps[step] if not done and step < total else "",
                }
            )
        return out

    # ── Progression ──────────────────────────────────────────────────────
    async def _async_advance_quests(self, child_id: str, chore_id: str) -> None:
        """Advance any active quest whose current step is ``chore_id``.

        Called after a (non-bonus) chore completion is approved. Persists and
        refreshes only if a quest actually advanced.
        """
        child = self.get_child(child_id)
        if not child:
            return
        changed = False
        for quest in self.storage.get_quests():
            if not quest.active or not quest.steps:
                continue
            if not self._quest_applies_to(quest, child_id):
                continue
            prog = dict(self.storage.get_quest_child_progress(quest.id, child_id))
            step = int(prog.get("step", 0))
            if step >= len(quest.steps):
                continue  # already finished (non-repeatable)
            if quest.steps[step] != chore_id:
                continue  # this completion isn't the next step
            step += 1
            prog["step"] = step
            self.storage.set_quest_child_progress(quest.id, child_id, prog)
            changed = True

            if step >= len(quest.steps):
                await self._complete_quest(quest, child, prog)

        if changed:
            await self.storage.async_save()
            await self.async_refresh()

    async def _complete_quest(self, quest: Quest, child, prog: dict) -> None:
        """Award the quest bonus and reset/finalise progress."""
        prog["completed_count"] = int(prog.get("completed_count", 0)) + 1
        prog["last_completed"] = dt_util.now().isoformat()

        bonus = int(quest.bonus_points or 0)
        if bonus > 0:
            child.points += bonus
            child.total_points_earned += bonus
            child.career_score = child.total_points_earned - child.total_penalties_received
            self.storage.add_points_transaction(
                PointsTransaction(
                    child_id=child.id,
                    points=bonus,
                    reason=f"Quest complete: {quest.name}",
                    created_at=dt_util.now(),
                )
            )
            if hasattr(self, "_maybe_level_up"):
                await self._maybe_level_up(child)
            self.storage.update_child(child)

        self.hass.bus.async_fire(
            "taskmate_quest_completed",
            {
                "child_id": child.id,
                "child_name": child.name,
                "quest_id": quest.id,
                "quest_name": quest.name,
                "bonus": bonus,
                "timestamp": dt_util.now().isoformat(),
            },
        )
        if hasattr(self, "_celebrate"):
            await self._celebrate(
                child,
                "quest_completed",
                f"{child.name} completed the quest '{quest.name}'!",
                tier=3,
                extra={"quest_id": quest.id, "bonus": bonus},
            )

        # Repeatable quests start over; one-shot quests stay complete.
        if quest.repeatable:
            prog["step"] = 0
        self.storage.set_quest_child_progress(quest.id, child.id, prog)
        _LOGGER.info("Quest '%s' completed by %s (+%d)", quest.name, child.name, bonus)
