"""
FreeExile Milestone 2 Empirical Challenger Test Suite:
Adversarial Verification of Static Linter (tools/lint/check_i18n_hygiene.py).

Verifies Negative Baselines and Edge Cases:
- Test 1: Injected hardcoded Vietnamese string in UI file -> code 1 under --strict (RULE-1-HARDCODED-VI).
- Test 2: Injected missing key in 1 of 9 languages in catalog / mock catalog -> code 1 (RULE-2-DICT-PARITY).
- Test 3: Injected dangling data-i18n key in HTML -> code 1 (RULE-3-DANGLING-KEY).
- Test 4: Current production codebase -> code 0 (100% clean).
- Test 5: Allowlisted seed structures and fallback arguments do NOT trigger false positives.
- Test 6: Single-line and multi-line comments do NOT trigger false positives.
- Test 7: JSON audit report generation format and data integrity.
- Test 8-11: Deep empirical edge-case boundary challenges (path resolution, multiline, braces).
"""

from __future__ import annotations

import json
import subprocess
import sys
import unittest
from pathlib import Path

PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
LINTER_SCRIPT = PROJECT_ROOT / "tools" / "lint" / "check_i18n_hygiene.py"
CHAT_UI_JS = PROJECT_ROOT / "client" / "webapp" / "js" / "ui" / "chat_ui.js"
CHAT_CATALOG_JS = PROJECT_ROOT / "client" / "webapp" / "js" / "data" / "chat_i18n_catalog.js"
INDEX_HTML = PROJECT_ROOT / "client" / "webapp" / "index.html"


def run_linter(*extra_args: str) -> subprocess.CompletedProcess[str]:
    """Executes check_i18n_hygiene.py via subprocess with utf-8 encoding."""
    cmd = [sys.executable, str(LINTER_SCRIPT)] + list(extra_args)
    return subprocess.run(
        cmd,
        capture_output=True,
        text=True,
        encoding="utf-8",
        cwd=str(PROJECT_ROOT),
        timeout=15.0,
    )


class TestI18nHygieneLinterAdversarial(unittest.TestCase):
    """Empirical negative and positive baseline verification of check_i18n_hygiene.py."""

    def test_01_negative_baseline_hardcoded_vietnamese_ui_fails_strict(self) -> None:
        """Test 1: Injected hardcoded Vietnamese string in UI file MUST exit with code 1."""
        temp_file = PROJECT_ROOT / "client" / "webapp" / "js" / "ui" / "temp_adversarial_vi.js"
        try:
            temp_file.write_text(
                'export function renderAlert() { return "Thông báo khẩn cấp"; }\n',
                encoding="utf-8",
            )
            res = run_linter("--strict", "--target-files", str(temp_file))
            self.assertEqual(res.returncode, 1, f"Expected code 1, got {res.returncode}")
            self.assertIn("RULE-1-HARDCODED-VI", res.stdout)
            self.assertIn("Hardcoded Vietnamese string detected", res.stdout)
            self.assertIn("Thông báo khẩn cấp", res.stdout)
        finally:
            if temp_file.exists():
                temp_file.unlink()

    def test_02_negative_baseline_missing_key_in_catalog_fails_strict(self) -> None:
        """Test 2: Injected missing key in 1 of 9 languages MUST trigger parity failure and code 1."""
        original_content = CHAT_CATALOG_JS.read_text(encoding="utf-8")
        try:
            # Delete one key from English dictionary in chat catalog
            target_entry = '"channel_world": "World",'
            self.assertIn(target_entry, original_content, "Target entry missing from catalog")
            modified = original_content.replace(target_entry, "", 1)
            CHAT_CATALOG_JS.write_text(modified, encoding="utf-8")

            res = run_linter("--strict")
            self.assertEqual(res.returncode, 1, f"Expected code 1, got {res.returncode}")
            self.assertIn("RULE-2-DICT-PARITY", res.stdout)
            self.assertIn("missing 1 keys", res.stdout)
            self.assertIn("channel_world", res.stdout)
        finally:
            CHAT_CATALOG_JS.write_text(original_content, encoding="utf-8")

    def test_03_negative_baseline_dangling_data_i18n_key_in_html_fails_strict(self) -> None:
        """Test 3: Injected dangling data-i18n key in HTML MUST detect missing key and exit code 1."""
        original_html = INDEX_HTML.read_text(encoding="utf-8")
        dangling_tag = '\n<div data-i18n="adversarial_dangling_ghost_key_999"></div>\n'
        try:
            INDEX_HTML.write_text(original_html + dangling_tag, encoding="utf-8")
            res = run_linter("--strict")
            self.assertEqual(res.returncode, 1, f"Expected code 1, got {res.returncode}")
            self.assertIn("RULE-3-DANGLING-KEY", res.stdout)
            self.assertIn("adversarial_dangling_ghost_key_999", res.stdout)
        finally:
            INDEX_HTML.write_text(original_html, encoding="utf-8")

    def test_04_positive_baseline_production_codebase_passes_strict(self) -> None:
        """Test 4: Current production codebase MUST pass cleanly with code 0."""
        res = run_linter("--strict")
        self.assertEqual(res.returncode, 0, f"Production codebase failed:\n{res.stdout}\n{res.stderr}")
        self.assertIn("SUCCESS: 100% i18n hygiene compliance", res.stdout)
        self.assertIn("[*] Rule 1 (Zero Hardcoded VI Strings) Violations: 0", res.stdout)
        self.assertIn("[*] Rule 2 (9-Language Parity) Violations        : 0", res.stdout)
        self.assertIn("[*] Rule 3 (Missing / Dangling Keys) Violations  : 0", res.stdout)

    def test_05_allowlisted_seed_structures_and_fallback_params_pass(self) -> None:
        """Rule 1 MUST allow fallback string arguments and seed dictionaries."""
        temp_file = PROJECT_ROOT / "client" / "webapp" / "js" / "ui" / "temp_allowlist_check.js"
        code = """
        export const CHANNELS = {
            world: { name: "Thế Giới" }
        };
        export const RARITY_INFO = {
            1: { name: "Phàm Phẩm" }
        };
        const text = t('my_key', null, 'Dữ liệu dự phòng');
        """
        try:
            temp_file.write_text(code, encoding="utf-8")
            res = run_linter("--strict", "--target-files", str(temp_file))
            self.assertEqual(res.returncode, 0, f"False positive on allowlisted code:\n{res.stdout}")
            self.assertIn("Violations: 0", res.stdout)
        finally:
            if temp_file.exists():
                temp_file.unlink()

    def test_06_comments_with_vietnamese_do_not_trigger_rule_1(self) -> None:
        """Rule 1 MUST ignore single-line and multi-line comments containing Vietnamese."""
        temp_file = PROJECT_ROOT / "client" / "webapp" / "js" / "ui" / "temp_comment_check.js"
        code = """
        // Chú thích tiếng Việt một dòng: Thế Giới
        /*
         * Chú thích tiếng Việt nhiều dòng:
         * Bang Hội, Đội Ngũ, Mật Thư
         */
        const key = "chat_btn_send";
        """
        try:
            temp_file.write_text(code, encoding="utf-8")
            res = run_linter("--strict", "--target-files", str(temp_file))
            self.assertEqual(res.returncode, 0, f"False positive on comments:\n{res.stdout}")
        finally:
            if temp_file.exists():
                temp_file.unlink()

    def test_07_mock_catalog_parity_checker_isolated(self) -> None:
        """Rule 2 isolated verification on a mock 9-language catalog with extra key in 'vi'."""
        sys.path.insert(0, str(PROJECT_ROOT))
        from tools.lint.check_i18n_hygiene import check_rule2_dictionary_parity

        mock_catalog = PROJECT_ROOT / "tests" / "temp_mock_catalog.js"
        mock_content = """
        export const MOCK_CATALOG = {
          vi: { "key_a": "A", "key_b": "B" },
          en: { "key_a": "A" },
          zh: { "key_a": "A", "key_b": "B" },
          ja: { "key_a": "A", "key_b": "B" },
          ko: { "key_a": "A", "key_b": "B" },
          th: { "key_a": "A", "key_b": "B" },
          de: { "key_a": "A", "key_b": "B" },
          ru: { "key_a": "A", "key_b": "B" },
          es: { "key_a": "A", "key_b": "B" }
        };
        """
        try:
            mock_catalog.write_text(mock_content, encoding="utf-8")
            violations = check_rule2_dictionary_parity(mock_catalog, "MOCK_CATALOG")
            self.assertEqual(len(violations), 1)
            self.assertIn("Locale 'en' missing 1 keys", violations[0].message)
            self.assertIn("key_b", violations[0].message)
        finally:
            if mock_catalog.exists():
                mock_catalog.unlink()

    def test_08_json_report_export_schema(self) -> None:
        """Linter with --json-out MUST write valid JSON matching report schema."""
        out_json = PROJECT_ROOT / "tests" / "temp_i18n_report.json"
        try:
            res = run_linter("--strict", "--json-out", str(out_json))
            self.assertEqual(res.returncode, 0)
            self.assertTrue(out_json.exists(), "JSON report file was not created")
            data = json.loads(out_json.read_text(encoding="utf-8"))
            self.assertIn("total_files_scanned", data)
            self.assertIn("total_errors", data)
            self.assertIn("rule1", data)
            self.assertIn("rule2", data)
            self.assertIn("rule3", data)
            self.assertEqual(data["total_errors"], 0)
        finally:
            if out_json.exists():
                out_json.unlink()

    def test_09_multiline_template_string_blindspot_investigation(self) -> None:
        """Adversarial stress: documents that multiline template literals bypass line regex."""
        temp_file = PROJECT_ROOT / "client" / "webapp" / "js" / "ui" / "temp_multiline.js"
        # String broken across lines lacks opening/closing quotes on inner line
        multiline_code = "const unlocalized = `\nTiếng Việt nhiều dòng\n`;\n"
        try:
            temp_file.write_text(multiline_code, encoding="utf-8")
            res = run_linter("--strict", "--target-files", str(temp_file))
            # Demonstrates current implementation blind spot: returns 0 because line scan misses it
            bypassed = (res.returncode == 0)
            self.assertTrue(bypassed, "Empirical proof of multiline template literal evasion")
        finally:
            if temp_file.exists():
                temp_file.unlink()

    def test_10_catalog_brace_in_string_parser_limit_investigation(self) -> None:
        """Adversarial stress: documents that naive brace matching truncates on '}' in strings."""
        sys.path.insert(0, str(PROJECT_ROOT))
        from tools.lint.check_i18n_hygiene import parse_catalog_keys

        mock_catalog = PROJECT_ROOT / "tests" / "temp_brace_catalog.js"
        mock_content = """
        export const MOCK = {
          vi: {
            "key1": "Value with closing } brace",
            "key2": "Second key",
            "key3": "Third key"
          }
        };
        """
        try:
            mock_catalog.write_text(mock_content, encoding="utf-8")
            keys = parse_catalog_keys(mock_catalog)
            vi_keys = keys.get("vi", set())
            # Truncated because '}' in string decrements brace counter prematurely
            self.assertIn("key1", vi_keys)
            self.assertNotIn("key2", vi_keys, "Empirical proof of brace truncation limitation")
        finally:
            if mock_catalog.exists():
                mock_catalog.unlink()


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