#!/usr/bin/env python3
"""
Axiom Zero — Comprehensive Test Suite: C++ Shims & UI State Mutations
Node 4 Unit Test Battery validating:
1. Gecko v142 -> v151 Dual-Brain C++ Phantom Shim & Prototype Oracle Evasion
2. Native toString() function forgery binding
3. Bot UA Database Phantom Shim registry integrity
4. GTK 3 Dev Control Center ListStore / TreeStore state mutations & filter transitions
5. Thread-safe GLib UI updates and KPI status badge mutations
6. Web Dev Portal HTML/JS UI state mutation models
"""

import unittest
import os
import sys
import json
import subprocess
import tempfile

# Ensure dev_control_center is in python path
DEV_CENTER_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, DEV_CENTER_DIR)
os.environ["NOCTUA_HEADLESS_TEST"] = "1"

try:
    from bot_ua_database import BOT_UA_DATABASE
except ImportError:
    BOT_UA_DATABASE = []


class TestCppShimsAndPhantomShim(unittest.TestCase):
    def setUp(self):
        self.shim_path = os.path.join(
            os.path.dirname(DEV_CENTER_DIR),
            "axiom-gauntlet",
            "src",
            "bots",
            "phantom_shim.js"
        )

    def test_01_phantom_shim_file_exists(self):
        """Verify Phantom Shim JS source exists in gauntlet package."""
        self.assertTrue(os.path.exists(self.shim_path), f"Phantom shim missing at {self.shim_path}")

    def test_02_phantom_shim_execution_and_forgery(self):
        """Verify Phantom Shim injects v151 properties and passes prototype oracle checks in Node.js."""
        test_script = """
        const fs = require('fs');
        const vm = require('vm');
        
        // Setup mock browser globals in VM context
        const context = vm.createContext({
            console: console,
            window: {},
            navigator: {},
            Object: Object,
            Function: Function,
            Promise: Promise,
            Array: Array,
            __filename: '""" + self.shim_path + """'
        });
        context.window = context;
        context.navigator = context.navigator;
        
        const shimContent = fs.readFileSync('""" + self.shim_path + """', 'utf8');
        // Evaluate module export
        const script = new vm.Script(shimContent + '\\n; window.PHANTOM_CODE = module.exports.code;');
        const moduleObj = { exports: {} };
        context.module = moduleObj;
        
        script.runInContext(context);
        
        // Execute inner shim IIFE code in context
        const innerScript = new vm.Script(moduleObj.exports.code);
        innerScript.runInContext(context);
        
        // Assertions inside VM context
        const results = vm.runInContext(`
            (function() {
                const ua = navigator.userAgent;
                const isWebDriver = navigator.webdriver;
                const hasCookieStore = typeof window.cookieStore !== 'undefined';
                
                // Test native toString forgery
                const uaPropDesc = Object.getOwnPropertyDescriptor(navigator, 'userAgent');
                const toStringVal = uaPropDesc.get.toString();
                const isNativeFormatted = toStringVal.includes('[native code]');
                
                return {
                    ua: ua,
                    isWebDriver: isWebDriver,
                    hasCookieStore: hasCookieStore,
                    toStringVal: toStringVal,
                    isNativeFormatted: isNativeFormatted
                };
            })()
        `, context);
        
        console.log(JSON.stringify(results));
        """
        
        with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False) as f:
            f.write(test_script)
            temp_js = f.name
            
        try:
            res = subprocess.run(["node", temp_js], capture_output=True, text=True)
            self.assertEqual(res.returncode, 0, f"Phantom Shim VM execution failed: {res.stderr}")
            last_line = res.stdout.strip().splitlines()[-1]
            data = json.loads(last_line)
            
            # Assertions
            self.assertIn("rv:151.0", data["ua"], "UserAgent must forge v151 identity")
            self.assertIn("Firefox/151.0", data["ua"], "UserAgent must match Firefox/151.0")
            self.assertFalse(data["isWebDriver"], "navigator.webdriver must return false")
            self.assertTrue(data["hasCookieStore"], "window.cookieStore API must be polyfilled for v151")
            self.assertTrue(data["isNativeFormatted"], "Property getters must return '[native code]' string representation")
        finally:
            if os.path.exists(temp_js):
                os.remove(temp_js)

    def test_03_bot_ua_database_phantom_shim_registry(self):
        """Verify bot_ua_database includes Gecko v142 -> v151 Phantom Shim definitions."""
        from bot_ua_database import BOT_UA_DATABASE
        
        matching_entries = [
            e for e in BOT_UA_DATABASE 
            if "phantomshim" in e.get("keywords", []) or "v151" in e.get("notes", "")
        ]
        self.assertGreater(len(matching_entries), 0, "Bot UA DB must contain Phantom Shim entry")
        entry = matching_entries[0]
        self.assertIn("Gecko", entry["name"])
        self.assertIn("v142", entry["notes"])
        self.assertIn("v151", entry["notes"])


class TestGTKAndUIStateMutations(unittest.TestCase):
    def setUp(self):
        import gi
        gi.require_version('Gtk', '3.0')
        from gi.repository import Gtk
        self.Gtk = Gtk

    def test_01_liststore_node_mutation_and_clear(self):
        """Test GTK ListStore row insertions, field mutations, and clear operations."""
        # schema: [node_id, domain, status, latency_ms, threat_score, icon_name]
        store = self.Gtk.ListStore(str, str, str, float, int, str)
        
        # Initial insertion
        iter1 = store.append(["node_us_east_01", "https://api1.noctualabs.tech", "ONLINE", 12.4, 0, "dialog-information"])
        self.assertEqual(len(store), 1)
        self.assertEqual(store[iter1][0], "node_us_east_01")
        
        # State mutation: node under attack
        store.set_value(iter1, 2, "ATTACK_BLOCKED")
        store.set_value(iter1, 3, 14.8)
        store.set_value(iter1, 4, 95)
        store.set_value(iter1, 5, "dialog-warning")
        
        self.assertEqual(store[iter1][2], "ATTACK_BLOCKED")
        self.assertEqual(store[iter1][4], 95)
        
        # Add second node
        iter2 = store.append(["node_eu_west_02", "https://api2.noctualabs.tech", "ONLINE", 8.2, 5, "dialog-information"])
        self.assertEqual(len(store), 2)
        
        # Clear store mutation
        store.clear()
        self.assertEqual(len(store), 0)

    def test_02_treestore_category_mutation(self):
        """Test GTK TreeStore hierarchical threat category mutations."""
        # schema: [category_name, block_count, percentage, status_class]
        store = self.Gtk.TreeStore(str, int, float, str)
        
        parent = store.append(None, ["HTTP Botnet Attacks", 1420, 68.5, "status-critical"])
        child1 = store.append(parent, ["cURL / Headless Scrapers", 850, 41.1, "status-warn"])
        child2 = store.append(parent, ["Phantom Shim Oracle Spoofers", 570, 27.4, "status-critical"])
        
        self.assertEqual(len(store), 1, "Top-level parent count must be 1")
        self.assertEqual(store.iter_n_children(parent), 2, "Child row count under parent must be 2")
        self.assertEqual(store[parent][1], 1420)
        
        # Mutate block count on child event
        store.set_value(child2, 1, 600)
        store.set_value(parent, 1, 1450)
        self.assertEqual(store[child2][1], 600)
        self.assertEqual(store[parent][1], 1450)

    def test_03_gtk_app_kpi_state_mutations(self):
        """Test AxiomDevControlGTK instance KPI metric updates and badge state styling."""
        from axiom_dev_control_gtk import AxiomDevControlGTK
        app = AxiomDevControlGTK()
        
        self.assertIsNotNone(app)
        self.assertTrue(hasattr(app, 'lbl_ipc_status'))
        
        # Test badge label mutation
        app.lbl_ipc_status.set_label("⚡ CLOUD HUB: CONNECTED (34 NODES)")
        self.assertIn("CONNECTED", app.lbl_ipc_status.get_label())
        
        app.destroy()

    def test_04_html_dev_portal_ui_structure_and_state(self):
        """Test dev_portal.html and executive_demo.html DOM structure and state script hooks."""
        portal_path = os.path.join(DEV_CENTER_DIR, "dev_portal.html")
        demo_path = os.path.join(os.path.dirname(DEV_CENTER_DIR), "executive_demo.html")
        
        self.assertTrue(os.path.exists(portal_path), "dev_portal.html must exist")
        self.assertTrue(os.path.exists(demo_path), "executive_demo.html must exist")
        
        with open(portal_path, 'r', encoding='utf-8') as f:
            content = f.read()
            self.assertIn("Axiom Zero", content)
            self.assertIn("stream", content.lower())
            
        with open(demo_path, 'r', encoding='utf-8') as f:
            demo_content = f.read()
            self.assertIn("Axiom Zero", demo_content)


if __name__ == "__main__":
    print("======================================================================")
    print("  AXIOM ZERO — C++ SHIMS & UI STATE MUTATION TEST SUITE")
    print("======================================================================")
    unittest.main(verbosity=2)
