"""Power commands for raising and lowering PL."""

import asyncio

from mudlib.combat.engine import get_encounter
from mudlib.combat.stamina import check_stamina_cues
from mudlib.commands import CommandDefinition, register
from mudlib.commands.movement import send_nearby_message
from mudlib.gmcp import send_char_vitals
from mudlib.player import Player

# Power-up mechanics
POWER_UP_TICK_INTERVAL = 0.05  # seconds between ticks
POWER_UP_PL_PER_TICK = 5.0  # PL increase per tick
POWER_UP_STAMINA_COST_PER_TICK = 2.0  # stamina cost per tick
POWER_DOWN_MIN_PL = 1.0  # minimum PL when powering down


async def _check_can_power_up(player: Player) -> str | None:
    """Return error message if player can't power up, else None."""
    # Check if already powering up
    if player._power_task is not None and not player._power_task.done():
        return "You're already powering up!\r\n"

    # Check for combat
    if get_encounter(player) is not None:
        return "You can't power up during combat!\r\n"

    # Check stamina
    if player.stamina <= 0:
        return "You don't have enough stamina to power up!\r\n"

    return None


async def power_up_loop(player: Player, target_pl: float | None = None) -> None:
    """Background task that powers up the player over time.

    Args:
        player: The player powering up
        target_pl: Optional target PL to stop at (defaults to max_pl)
    """
    target = target_pl if target_pl is not None else player.max_pl

    try:
        while player.pl < target and player.stamina > 0:
            await asyncio.sleep(POWER_UP_TICK_INTERVAL)

            # Deduct stamina
            player.stamina -= POWER_UP_STAMINA_COST_PER_TICK
            if player.stamina < 0:
                player.stamina = 0.0

            # Increase PL
            old_pl = player.pl
            player.pl = min(player.pl + POWER_UP_PL_PER_TICK, target)

            # Send GMCP update
            send_char_vitals(player)

            # Check stamina cues after deduction
            await check_stamina_cues(player)

            # Send periodic aura message (every ~0.2s)
            if int(old_pl / 20) != int(player.pl / 20):
                await player.send("Your aura flares!\r\n")
                await send_nearby_message(
                    player, player.x, player.y, f"{player.name}'s aura flares!\r\n"
                )

            # Check if we've run out of stamina
            if player.stamina <= 0:
                await player.send("You run out of stamina and stop powering up.\r\n")
                await send_nearby_message(
                    player,
                    player.x,
                    player.y,
                    f"{player.name} stops powering up.\r\n",
                )
                break

        # Reached target
        if player.pl >= target:
            await player.send(f"You reach your target power level of {target}.\r\n")
            await send_nearby_message(
                player,
                player.x,
                player.y,
                f"{player.name} reaches their target power level.\r\n",
            )
    finally:
        # Clean up the task reference
        player._power_task = None


async def cmd_power(player: Player, args: str) -> None:
    """Manage power level.

    Usage:
        power up - raise PL toward max_pl
        power down - lower PL to minimum
        power <number> - set PL to exact value
        power stop - cancel ongoing power-up
    """
    if not args:
        await player.send("Usage: power up|down|stop|<number>\r\n")
        return

    args = args.strip().lower()

    # Handle power stop
    if args == "stop":
        if player._power_task is None or player._power_task.done():
            await player.send("You're not powering up.\r\n")
            return

        player._power_task.cancel()
        player._power_task = None
        await player.send("You stop powering up.\r\n")
        await send_nearby_message(
            player, player.x, player.y, f"{player.name} stops powering up.\r\n"
        )
        return

    # Handle power down
    if args == "down":
        # Cancel any ongoing power-up
        if player._power_task is not None and not player._power_task.done():
            player._power_task.cancel()
            player._power_task = None

        player.pl = POWER_DOWN_MIN_PL
        send_char_vitals(player)
        await player.send(f"You power down to PL {POWER_DOWN_MIN_PL}.\r\n")
        await send_nearby_message(
            player, player.x, player.y, f"{player.name} powers down.\r\n"
        )
        return

    # Handle power up
    if args == "up":
        error = await _check_can_power_up(player)
        if error:
            await player.send(error)
            return

        # Check if already at max
        if player.pl >= player.max_pl:
            await player.send("You're already at maximum power!\r\n")
            return

        # Start power-up loop
        await player.send("You begin powering up...\r\n")
        await send_nearby_message(
            player, player.x, player.y, f"{player.name} begins powering up!\r\n"
        )
        player._power_task = asyncio.create_task(power_up_loop(player))
        return

    # Handle power <number>
    try:
        target = float(args)
    except ValueError:
        await player.send(f"Invalid power level: {args}\r\n")
        return

    # Validate target
    if target <= 0:
        await player.send("Power level must be greater than 0.\r\n")
        return

    if target > player.max_pl:
        await player.send(
            f"Power level cannot exceed your maximum of {player.max_pl}.\r\n"
        )
        return

    # If target is lower than current, instant change
    if target < player.pl:
        # Cancel any ongoing power-up
        if player._power_task is not None and not player._power_task.done():
            player._power_task.cancel()
            player._power_task = None

        player.pl = target
        send_char_vitals(player)
        await player.send(f"You lower your power to {target}.\r\n")
        await send_nearby_message(
            player, player.x, player.y, f"{player.name} lowers their power.\r\n"
        )
        return

    # If target is higher, start power-up loop to that target
    if target > player.pl:
        error = await _check_can_power_up(player)
        if error:
            await player.send(error)
            return

        # Start power-up loop to target
        await player.send(f"You begin powering up to {target}...\r\n")
        await send_nearby_message(
            player, player.x, player.y, f"{player.name} begins powering up!\r\n"
        )
        player._power_task = asyncio.create_task(power_up_loop(player, target))
        return

    # Target equals current PL
    await player.send(f"You're already at PL {target}.\r\n")


register(CommandDefinition("power", cmd_power, aliases=["pow"]))
