"""Combat encounter and state machine."""

import time
from dataclasses import dataclass
from enum import Enum

from mudlib.combat.moves import CombatMove
from mudlib.entity import Entity


class CombatState(Enum):
    """States of the combat state machine."""

    IDLE = "idle"
    TELEGRAPH = "telegraph"
    WINDOW = "window"
    RESOLVE = "resolve"


# Telegraph phase duration in seconds (3 game ticks at 100ms/tick)
TELEGRAPH_DURATION = 0.3

# Seconds of no action before combat fizzles out
IDLE_TIMEOUT = 30.0


@dataclass
class ResolveResult:
    """Result of resolving a combat exchange."""

    resolve_template: str  # POV template for resolve message
    damage: float
    countered: bool
    combat_ended: bool


@dataclass
class CombatEncounter:
    """Represents an active combat encounter between two entities."""

    attacker: Entity
    defender: Entity
    state: CombatState = CombatState.IDLE
    current_move: CombatMove | None = None
    move_started_at: float = 0.0
    pending_defense: CombatMove | None = None
    last_action_at: float = 0.0

    def attack(self, move: CombatMove) -> None:
        """Initiate or switch an attack move.

        If called during TELEGRAPH or WINDOW, switches to the new move
        without resetting the timer. Refunds old move's stamina cost.

        Args:
            move: The attack move to execute
        """
        now = time.monotonic()

        if self.state in (CombatState.TELEGRAPH, CombatState.WINDOW):
            # Switching — refund old cost, keep timer
            if self.current_move:
                self.attacker.stamina = min(
                    self.attacker.stamina + self.current_move.stamina_cost,
                    self.attacker.max_stamina,
                )
        else:
            # First attack — start timer
            self.move_started_at = now

        self.current_move = move
        self.attacker.stamina -= move.stamina_cost
        self.last_action_at = now
        if self.state == CombatState.IDLE:
            self.state = CombatState.TELEGRAPH

    def defend(self, move: CombatMove) -> None:
        """Queue a defense move on the encounter.

        Stamina cost and lock are handled by the command layer (do_defend).

        Args:
            move: The defense move to attempt
        """
        self.pending_defense = move
        self.last_action_at = time.monotonic()

    def tick(self, now: float) -> None:
        """Advance the state machine based on current time.

        Args:
            now: Current time from monotonic clock
        """
        if self.state == CombatState.TELEGRAPH:
            # Check if telegraph phase is over
            elapsed = now - self.move_started_at
            if elapsed >= TELEGRAPH_DURATION:
                self.state = CombatState.WINDOW

        elif self.state == CombatState.WINDOW:
            # Check if timing window has expired
            if self.current_move is None:
                return

            elapsed = now - self.move_started_at
            window_seconds = self.current_move.timing_window_ms / 1000.0
            total_time = TELEGRAPH_DURATION + window_seconds

            if elapsed >= total_time:
                self.state = CombatState.RESOLVE

    def resolve(self) -> ResolveResult:
        """Resolve the combat exchange and return result.

        Returns:
            ResolveResult with template for both participants
        """
        if self.current_move is None:
            return ResolveResult(
                resolve_template="No active move to resolve.",
                damage=0.0,
                countered=False,
                combat_ended=False,
            )

        # Check for altitude mismatch (flying dodge)
        attacker_flying = getattr(self.attacker, "flying", False)
        defender_flying = getattr(self.defender, "flying", False)
        if attacker_flying != defender_flying:
            # Altitude mismatch - attack misses
            template = "{attacker} miss{es} — {defender} {are|is} out of reach!"
            # Reset to IDLE
            self.state = CombatState.IDLE
            self.current_move = None
            self.pending_defense = None
            return ResolveResult(
                resolve_template=template,
                damage=0.0,
                countered=True,
                combat_ended=False,
            )

        # Check if defense counters attack
        defense_succeeds = (
            self.pending_defense
            and self.pending_defense.name in self.current_move.countered_by
        )
        if defense_succeeds:
            # Successful counter - no damage
            damage = 0.0
            template = (
                self.current_move.resolve_miss
                if self.current_move.resolve_miss
                else "{defender} counter{s} {attacker}'s attack!"
            )
            countered = True
        elif self.pending_defense:
            # Wrong defense - normal damage
            damage = self.attacker.pl * self.current_move.damage_pct
            self.defender.pl -= damage
            template = (
                self.current_move.resolve_hit
                if self.current_move.resolve_hit
                else "{attacker}'s attack hit{s} {defender} for damage!"
            )
            countered = False
        else:
            # No defense - increased damage
            damage = self.attacker.pl * self.current_move.damage_pct * 1.5
            self.defender.pl -= damage
            template = (
                self.current_move.resolve_hit
                if self.current_move.resolve_hit
                else "{attacker}'s attack hit{s} {defender} for damage!"
            )
            countered = False

        # Check for combat end conditions
        combat_ended = self.defender.pl <= 0 or self.attacker.stamina <= 0

        # Reset to IDLE
        self.state = CombatState.IDLE
        self.current_move = None
        self.pending_defense = None

        return ResolveResult(
            resolve_template=template,
            damage=damage,
            countered=countered,
            combat_ended=combat_ended,
        )
