"""
Unit Tests for Server-Authoritative Zone Monster and Quest Synchronization.
Verifies that monster stats, level 1 balance, bat swarms, and quest progress
are strictly determined and streamed from the Server Gateway to the Client.
Zero-Trust Server Authority: No client-side authority for monster stats or quest progression.
"""

from __future__ import annotations

import asyncio
import json
import os
import sys
import unittest

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../server")))

import websockets
from gateway.ws_gateway_bridge import WsGatewayBridge


class TestWsAuthoritativeSync(unittest.IsolatedAsyncioTestCase):
    @classmethod
    def setUpClass(cls):
        cls.port = 18081
        cls.host = "127.0.0.1"
        cls.bridge = WsGatewayBridge(host=cls.host, port=cls.port)
        cls.bridge.start_background()

    @classmethod
    def tearDownClass(cls):
        cls.bridge.stop_background()

    async def test_server_authoritative_quest_sync_and_progress(self):
        """Verifies server streams canonical quest state and authoritatively handles kills."""
        uri = f"ws://{self.host}:{self.port}"
        async with websockets.connect(uri) as ws:
            welcome_raw = await ws.recv()
            welcome = json.loads(welcome_raw)
            self.assertEqual(welcome.get("type"), "welcome")

            # 1. Request quest sync for player_1
            await ws.send(json.dumps({
                "type": "request_quest_sync",
                "player_id": "player_sync_test"
            }))
            quest_raw = await ws.recv()
            quest_data = json.loads(quest_raw)
            self.assertEqual(quest_data.get("type"), "quest_sync")
            self.assertTrue(quest_data.get("authoritative"))
            quests = quest_data.get("quests", [])
            self.assertGreaterEqual(len(quests), 1)

            q1 = next((q for q in quests if q["quest_id"] == "quest_norm_01_thuc_tinh_tang_kiem"), None)
            self.assertIsNotNone(q1)
            self.assertEqual(q1["status"], "IN_PROGRESS")
            self.assertEqual(q1["objectives"][0]["current"], 0)
            self.assertEqual(q1["objectives"][0]["required"], 5)

            # 2. Report killing 1 starter bat
            await ws.send(json.dumps({
                "type": "monster_kill_report",
                "player_id": "player_sync_test",
                "monster_id": "mob_starter_bat"
            }))
            update_raw = await ws.recv()
            update_data = json.loads(update_raw)
            self.assertEqual(update_data.get("type"), "quest_progress_update")
            self.assertIn("quest_norm_01_thuc_tinh_tang_kiem", update_data.get("updated_quest_ids", []))
            u_q1 = next(q for q in update_data["quests"] if q["quest_id"] == "quest_norm_01_thuc_tinh_tang_kiem")
            self.assertEqual(u_q1["objectives"][0]["current"], 1)

            # 3. Kill 4 more starter hounds to complete the quest
            for i in range(2, 6):
                await ws.send(json.dumps({
                    "type": "monster_kill_report",
                    "player_id": "player_sync_test",
                    "monster_id": "mob_starter_hound"
                }))
                prog_raw = await ws.recv()
                prog_data = json.loads(prog_raw)
                p_q1 = next(q for q in prog_data["quests"] if q["quest_id"] == "quest_norm_01_thuc_tinh_tang_kiem")
                self.assertEqual(p_q1["objectives"][0]["current"], i)

            # Verify completion
            self.assertEqual(p_q1["status"], "COMPLETED")
            self.assertTrue(p_q1["objectives"][0]["is_completed"])

    async def test_server_authoritative_zone_monsters_level_1_balance(self):
        """Verifies server generates balanced level 1 monster packs with Quỷ Dơi (bats)."""
        uri = f"ws://{self.host}:{self.port}"
        async with websockets.connect(uri) as ws:
            await ws.recv()  # welcome

            # Request zone monsters for zone_tang_kiem_nhai
            await ws.send(json.dumps({
                "type": "request_zone_data",
                "zone_id": "zone_tang_kiem_nhai"
            }))
            zone_raw = await ws.recv()
            zone_data = json.loads(zone_raw)
            self.assertEqual(zone_data.get("type"), "zone_monsters_sync")
            self.assertEqual(zone_data.get("zone_id"), "zone_tang_kiem_nhai")
            self.assertTrue(zone_data.get("authoritative"))

            monsters = zone_data.get("monsters", [])
            self.assertGreaterEqual(len(monsters), 10)

            # Check starter pack 1 (Level 1 minions)
            p1_minions = [m for m in monsters if m["id"].startswith("srv_pack_zone_tang_kiem_nhai_1_m")]
            self.assertGreaterEqual(len(p1_minions), 3)

            for m in p1_minions:
                self.assertEqual(m["level"], 1)
                self.assertEqual(m["rank"], "COMMON")
                # Minion damage is strictly balanced: 4.0 <= dmg <= 8.5
                self.assertGreaterEqual(m["attack_dmg"], 4.0)
                self.assertLessEqual(m["attack_dmg"], 8.5)
                # Against 150 player HP, minion takes >= 17 hits to kill player
                hits_to_kill = 150.0 / m["attack_dmg"]
                self.assertGreaterEqual(hits_to_kill, 17.0)

            # Check that bat genus (CORRUPTED_RAPTOR) is present in zone
            has_bats = any(m["genus"] == "CORRUPTED_RAPTOR" for m in monsters)
            self.assertTrue(has_bats, "Zone must include native CORRUPTED_RAPTOR (bats)")


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