"""
Unit Test Suite for FreeExile Chat Channel Permission Gating.
Validates Guild, Party, World Level, Recruit Level, and System channel restrictions
across Server (ChannelManager), Client WebApp (chat_ui.js), and Client Engine (ChatManager.ts).
"""

from __future__ import annotations

import json
import subprocess
import unittest
from pathlib import Path
from typing import Any

from server.chat.channel_manager import ChannelManager
from server.chat.chat_types import ChatChannelType, SendChatRequestDTO

PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
CHAT_UI_JS = PROJECT_ROOT / "client" / "webapp" / "js" / "ui" / "chat_ui.js"
CHAT_MANAGER_TS = PROJECT_ROOT / "client" / "src" / "chat" / "ChatManager.ts"


class TestChatChannelPermissions(unittest.TestCase):
    """Verifies strict channel permissions on both server and client."""

    def setUp(self) -> None:
        self.channel_mgr = ChannelManager()

    def _eval_chat_ui_node(self, script: str) -> Any:
        escaped_path = str(CHAT_UI_JS).replace("\\", "/")
        full_script = f"""
        import('file:///{escaped_path}').then(chatModule => {{
            {script}
        }}).catch(err => {{
            console.error(err);
            process.exit(1);
        }});
        """
        res = subprocess.run(
            ["node", "--input-type=module"],
            input=full_script.encode("utf-8"),
            capture_output=True
        )
        if res.returncode != 0:
            raise RuntimeError(f"Node execution failed:\n{res.stderr.decode('utf-8', errors='replace')}")
        out = res.stdout.decode("utf-8").strip()
        return json.loads(out) if out else None

    # --- Group 1: Server-Side Validation (ChannelManager) ---

    def test_01_server_rejects_guild_chat_without_guild(self) -> None:
        """Server must strictly reject Guild chat if player has no guild_id."""
        req = SendChatRequestDTO(
            sender_id=1001,
            sender_name="Tân Thủ",
            sender_level=25,
            channel=ChatChannelType.GUILD,
            content="Xin chào bang hội",
            guild_id=None
        )
        allowed, reason, _ = self.channel_mgr.validate_send_permission(req)
        self.assertFalse(allowed)
        self.assertEqual(reason, "Chưa gia nhập Bang Hội.")

    def test_02_server_allows_guild_chat_with_guild(self) -> None:
        """Server must accept Guild chat if player belongs to a guild."""
        req = SendChatRequestDTO(
            sender_id=1001,
            sender_name="Đầu Lĩnh",
            sender_level=25,
            channel=ChatChannelType.GUILD,
            content="Tập hợp bang hội!",
            guild_id="guild_feral_01"
        )
        allowed, reason, _ = self.channel_mgr.validate_send_permission(req)
        self.assertTrue(allowed)
        self.assertEqual(reason, "")

    def test_03_server_rejects_party_chat_without_party(self) -> None:
        """Server must reject Party chat if player has no party_id."""
        req = SendChatRequestDTO(
            sender_id=1002,
            sender_name="Hiệp Khách",
            sender_level=30,
            channel=ChatChannelType.PARTY,
            content="Đi bí cảnh không?",
            party_id=None
        )
        allowed, reason, _ = self.channel_mgr.validate_send_permission(req)
        self.assertFalse(allowed)
        self.assertEqual(reason, "Chưa gia nhập Tổ Đội.")

    def test_04_server_rejects_world_chat_under_level_20(self) -> None:
        """Server must gate World chat at level 20."""
        req = SendChatRequestDTO(
            sender_id=1003,
            sender_name="Tân Thủ",
            sender_level=19,
            channel=ChatChannelType.WORLD,
            content="Tìm sư phụ"
        )
        allowed, reason, _ = self.channel_mgr.validate_send_permission(req)
        self.assertFalse(allowed)
        self.assertIn("Cần đạt cấp 20", reason)

    def test_05_server_rejects_whisper_to_self(self) -> None:
        """Server must reject any attempt to whisper to oneself."""
        req = SendChatRequestDTO(
            sender_id=1004,
            sender_name="Hiệp Khách",
            sender_level=100,
            channel=ChatChannelType.WHISPER,
            target_id=1004,
            content="Tự nói một mình"
        )
        allowed, reason, _ = self.channel_mgr.validate_send_permission(req)
        self.assertFalse(allowed)
        self.assertIn("Không thể tự gửi mật thư cho chính mình", reason)

    # --- Group 2: Client-Side WebApp Validation (chat_ui.js) ---

    def test_06_webapp_validate_channel_permission_matrix(self) -> None:
        """chat_ui.js validateChannelPermission must enforce Guild, Party, Level, and System constraints."""
        data = self._eval_chat_ui_node("""
            const rGuildNo = chatModule.validateChannelPermission(3, { guildId: '', partyId: '', level: 50 });
            const rGuildYes = chatModule.validateChannelPermission(3, { guildId: 'g_1', partyId: '', level: 50 });
            const rPartyNo = chatModule.validateChannelPermission(4, { guildId: '', partyId: '', level: 50 });
            const rPartyYes = chatModule.validateChannelPermission(4, { guildId: '', partyId: 'p_1', level: 50 });
            const rWorldLow = chatModule.validateChannelPermission(1, { guildId: '', partyId: '', level: 19 });
            const rWorldOk = chatModule.validateChannelPermission(1, { guildId: '', partyId: '', level: 20 });
            const rRecruitLow = chatModule.validateChannelPermission(7, { guildId: '', partyId: '', level: 9 });
            const rRecruitOk = chatModule.validateChannelPermission(7, { guildId: '', partyId: '', level: 10 });
            const rSystem = chatModule.validateChannelPermission(6, { guildId: 'g_1', partyId: 'p_1', level: 100 });
            console.log(JSON.stringify({
                rGuildNo, rGuildYes, rPartyNo, rPartyYes, rWorldLow, rWorldOk, rRecruitLow, rRecruitOk, rSystem
            }));
        """)
        self.assertFalse(data["rGuildNo"]["allowed"])
        self.assertEqual(data["rGuildNo"]["reason"], "Chưa gia nhập Bang Hội.")
        self.assertTrue(data["rGuildYes"]["allowed"])

        self.assertFalse(data["rPartyNo"]["allowed"])
        self.assertEqual(data["rPartyNo"]["reason"], "Chưa gia nhập Tổ Đội.")
        self.assertTrue(data["rPartyYes"]["allowed"])

        self.assertFalse(data["rWorldLow"]["allowed"])
        self.assertIn("Cần đạt cấp 20", data["rWorldLow"]["reason"])
        self.assertTrue(data["rWorldOk"]["allowed"])

        self.assertFalse(data["rRecruitLow"]["allowed"])
        self.assertIn("Cần đạt cấp 10", data["rRecruitLow"]["reason"])
        self.assertTrue(data["rRecruitOk"]["allowed"])

        self.assertFalse(data["rSystem"]["allowed"])
        self.assertIn("Kênh Hệ Thống", data["rSystem"]["reason"])

    def test_07_webapp_send_chat_blocked_with_system_warning(self) -> None:
        """When sending on Guild channel without a guild, chat_ui.js must emit a [Cảnh Báo] notice."""
        mock_dom = """
        const _createEl = (tag) => ({
            tagName: tag, id: '', className: '', innerHTML: '', textContent: '', value: '', children: [],
            classList: { _c: new Set(), add(c){this._c.add(c);}, remove(c){this._c.delete(c);}, contains(c){return this._c.has(c);} },
            appendChild(c){ this.children.push(c); return c; }, querySelectorAll(){ return []; }, addEventListener(){}
        });
        const elements = {};
        global.document = {
            getElementById: (id) => elements[id] || (elements[id] = _createEl('div')),
            querySelectorAll: () => [],
            createElement: (t) => _createEl(t)
        };
        global.window = { player: { guildId: '', partyId: '', level: 1, name: 'Tân Thủ' } };
        """
        data = self._eval_chat_ui_node(f"""
            {mock_dom}
            const input = document.getElementById('chat-input');
            input.value = 'Xin chào bang chúng!';
            chatModule.switchChannel(3); // Switch to Guild channel
            chatModule.sendCurrentChatMessage();

            const guildHistory = chatModule.getChannelHistory(3);
            const lastMsg = guildHistory[guildHistory.length - 1];
            console.log(JSON.stringify({{
                historyLength: guildHistory.length,
                sender: lastMsg?.senderName,
                content: lastMsg?.displayContent,
                inputVal: input.value
            }}));
        """)
        self.assertEqual(data["sender"], "Hệ Thống")
        self.assertIn("[Cảnh Báo] Chưa gia nhập Bang Hội.", data["content"])
        self.assertEqual(data["inputVal"], "", "Input must be cleared after warning")

    def test_08_webapp_send_chat_succeeds_when_guild_member(self) -> None:
        """When player is in a guild, sending on Guild channel must succeed normally."""
        mock_dom = """
        const _createEl = (tag) => ({
            tagName: tag, id: '', className: '', innerHTML: '', textContent: '', value: '', children: [],
            classList: { _c: new Set(), add(c){this._c.add(c);}, remove(c){this._c.delete(c);}, contains(c){return this._c.has(c);} },
            appendChild(c){ this.children.push(c); return c; }, querySelectorAll(){ return []; }, addEventListener(){}
        });
        const elements = {};
        global.document = {
            getElementById: (id) => elements[id] || (elements[id] = _createEl('div')),
            querySelectorAll: () => [],
            createElement: (t) => _createEl(t)
        };
        global.window = { player: { guildId: 'guild_iron_fist', partyId: '', level: 25, name: 'Độc Cô Cầu Bại' } };
        """
        data = self._eval_chat_ui_node(f"""
            {mock_dom}
            const input = document.getElementById('chat-input');
            input.value = 'Đồng đạo tập hợp phụ bản!';
            chatModule.switchChannel(3); // Guild channel
            chatModule.sendCurrentChatMessage();

            const guildHistory = chatModule.getChannelHistory(3);
            const lastMsg = guildHistory[guildHistory.length - 1];
            console.log(JSON.stringify({{
                historyLength: guildHistory.length,
                sender: lastMsg?.senderName,
                content: lastMsg?.displayContent,
                inputVal: input.value
            }}));
        """)
        self.assertEqual(data["sender"], "Độc Cô Cầu Bại")
        self.assertEqual(data["content"], "Đồng đạo tập hợp phụ bản!")
        self.assertEqual(data["inputVal"], "")

    # --- Group 3: TypeScript Client Engine (ChatManager.ts) ---

    def test_09_typescript_chat_manager_permission_api(self) -> None:
        """ChatManager.ts must provide validateSendPermission and level gates."""
        ts_code = CHAT_MANAGER_TS.read_text(encoding="utf-8")
        self.assertIn("validateSendPermission", ts_code)
        self.assertIn("MIN_WORLD_LEVEL = 20", ts_code)
        self.assertIn("MIN_RECRUIT_LEVEL = 10", ts_code)
        self.assertIn("PlayerChatContext", ts_code)
        self.assertIn("ChannelPermissionResult", ts_code)


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