"""
Unit and Integration Test Suite for FreeExile Milestone 4 Monster Poise, Stagger & Leashing:
- Monster poise recovery and safety reset (m.staggerTimer <= 0 && m.poise <= 0 -> m.poise = m.maxPoise)
- Stagger window attack cancellation (cancelTelegraph(m.id)) and action unlock
- Authoritative AI leashing persistent state (m.isLeashing latch)
- Leash return invulnerability and 30%/s HP regeneration
- Server MonsterAIEngine parity (30%/s regen, 1.5x speed, invulnerability, arrival reset)
- CombatSkills invulnerability protection against hitting leashing monsters
- Strict hygiene compliance (monster_system.js <= 500 lines hard cap)
"""

import sys
import unittest
from pathlib import Path

PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
SERVER_DIR = PROJECT_ROOT / "server"
if str(SERVER_DIR) not in sys.path:
    sys.path.insert(0, str(SERVER_DIR))
if str(PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(PROJECT_ROOT))

from world.monster_ai_engine import MonsterAIEngine
from world.monster_types import MonsterRuntimeState, MonsterAIState
from world.monster_procedural_engine import MonsterProceduralEngine

WEBAPP_DIR = PROJECT_ROOT / "client" / "webapp"
MONSTER_SYSTEM_JS = WEBAPP_DIR / "js" / "engine" / "monster_system.js"
COMBAT_SKILLS_JS = WEBAPP_DIR / "js" / "engine" / "combat_skills.js"


class TestMonsterPoiseAndLeash(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.monster_system_content = MONSTER_SYSTEM_JS.read_text(encoding="utf-8")
        cls.combat_skills_content = COMBAT_SKILLS_JS.read_text(encoding="utf-8")

    # --- Area 1: Client WebApp Poise & Stagger Logic ---
    def test_monster_system_poise_recovery_on_timer_expiry(self):
        """Verifies monster_system.js restores poise to maxPoise when staggerTimer expires or poise <= 0."""
        self.assertIn("if (m.staggerTimer <= 0) m.poise = m.maxPoise;", self.monster_system_content)
        self.assertIn("else if (m.poise <= 0)", self.monster_system_content)
        self.assertIn("m.poise = m.maxPoise;", self.monster_system_content)

    def test_monster_system_stagger_cancels_active_telegraph(self):
        """Verifies monster_system.js cancels active attack telegraph during stagger window."""
        self.assertIn("cancelTelegraph(m.id)", self.monster_system_content)

    def test_monster_system_stagger_unlocks_action(self):
        """Verifies monster animation action lock is released on stagger."""
        self.assertIn("m.anim.isActionLocked = false", self.monster_system_content)

    # --- Area 2: Client WebApp AI Leash Return ---
    def test_monster_system_leash_return_persistent_state(self):
        """Verifies monster_system.js holds isLeashing state until reaching origin threshold."""
        self.assertIn("m.isLeashing", self.monster_system_content)
        self.assertIn("distToOrigin <= 0.6", self.monster_system_content)

    def test_monster_system_leash_return_invulnerability_and_hp_regen(self):
        """Verifies monster_system.js grants invulnerability and 30%/s HP regen during leash return."""
        self.assertIn("m.isInvulnerable = true", self.monster_system_content)
        self.assertIn("0.30 * dt", self.monster_system_content)

    def test_monster_system_leash_cancels_telegraph(self):
        """Verifies active telegraph is cancelled when monster enters leash return."""
        self.assertIn("if (typeof cancelTelegraph === 'function') cancelTelegraph(m.id);", self.monster_system_content)

    # --- Area 3: CombatSkills Hit Protection ---
    def test_combat_skills_invulnerable_target_guard(self):
        """Verifies combat_skills.js guards against damaging invulnerable targets."""
        self.assertIn("target.isInvulnerable", self.combat_skills_content)

    # --- Area 4: Server-Side MonsterAIEngine Parity ---
    def test_server_ai_leash_return_grants_invulnerability_and_regen(self):
        """Verifies server MonsterAIEngine grants invulnerability and 30%/s HP regen."""
        ai_engine = MonsterAIEngine()
        monster = MonsterProceduralEngine().generate_monster(1, target_level=10)
        initial_hp = monster.max_hp * 0.2
        state = MonsterRuntimeState(
            monster=monster,
            current_x=20.0,
            current_y=20.0,
            current_hp=initial_hp,
            spawn_x=0.0,
            spawn_y=0.0,
            ai_state=MonsterAIState.LEASH_RETURN
        )
        action = ai_engine.update(state, player_pos=(20.0, 20.0), dt=1.0)
        self.assertEqual(action.action_type, "RETURN_TO_SPAWN")
        self.assertTrue(state.is_invulnerable)
        expected_hp = min(monster.max_hp, initial_hp + monster.max_hp * 0.30 * 1.0)
        self.assertAlmostEqual(state.current_hp, expected_hp)

    def test_server_ai_leash_return_arrival_resets_idle(self):
        """Verifies server MonsterAIEngine resets to IDLE and removes invulnerability when arriving at spawn."""
        ai_engine = MonsterAIEngine()
        monster = MonsterProceduralEngine().generate_monster(2, target_level=10)
        state = MonsterRuntimeState(
            monster=monster,
            current_x=0.5,
            current_y=0.5,
            current_hp=monster.max_hp,
            spawn_x=0.0,
            spawn_y=0.0,
            ai_state=MonsterAIState.LEASH_RETURN
        )
        action = ai_engine.update(state, player_pos=(15.0, 15.0), dt=1.0)
        self.assertEqual(action.action_type, "STAND")
        self.assertEqual(state.ai_state, MonsterAIState.IDLE)
        self.assertFalse(state.is_invulnerable)
        self.assertEqual(state.current_x, 0.0)
        self.assertEqual(state.current_y, 0.0)

    # --- Area 5: Strict Code Hygiene Compliance ---
    def test_monster_system_line_count_within_hygiene_limit(self):
        """Verifies client/webapp/js/engine/monster_system.js is <= 490 lines (strict cap 500)."""
        lines = self.monster_system_content.splitlines()
        self.assertLessEqual(len(lines), 490, f"monster_system.js line count {len(lines)} exceeds 490 lines target!")
        self.assertLessEqual(len(lines), 500, f"monster_system.js line count {len(lines)} exceeds 500 hard cap!")


if __name__ == "__main__":
    unittest.main()
