import os
import sys
import json
import asyncio
from playwright.async_api import async_playwright

if hasattr(sys.stdout, "reconfigure"):
    sys.stdout.reconfigure(encoding="utf-8")
if hasattr(sys.stderr, "reconfigure"):
    sys.stderr.reconfigure(encoding="utf-8")

async def run_adversarial_verification():
    html_path = os.path.abspath('app/static/bim/index.html').replace('\\', '/')
    file_url = f'file:///{html_path}'
    print(f'=== ADVERSARIAL VERIFICATION START ===')
    print(f'Target URL: {file_url}')

    async with async_playwright() as p:
        browser = await p.chromium.launch(channel="chrome", headless=True)
        page = await browser.new_page(viewport={"width": 1920, "height": 1080})
        
        console_errors = []
        page.on('console', lambda msg: console_errors.append(msg.text) if msg.type == 'error' else None)
        page.on('pageerror', lambda exc: console_errors.append(str(exc)))

        await page.goto(file_url, wait_until='load')
        await page.wait_for_timeout(1500)

        # 1. Inspect Three.js Scene Graph in groupDimensions
        scene_inspection = await page.evaluate('''() => {
            if (typeof window.groupDimensions === 'undefined') {
                return { error: 'window.groupDimensions is undefined' };
            }
            const grp = window.groupDimensions;
            let sprites = [];
            let lineSegments = [];
            let unknown = [];

            grp.children.forEach((c, idx) => {
                if (c.isSprite) {
                    sprites.push({
                        index: idx,
                        label: (c.userData && c.userData.label) ? c.userData.label : '',
                        position: { x: c.position.x, y: c.position.y, z: c.position.z },
                        scale: { x: c.scale.x, y: c.scale.y, z: c.scale.z },
                        renderOrder: c.renderOrder,
                        visible: c.visible,
                        material: {
                            depthTest: c.material.depthTest,
                            depthWrite: c.material.depthWrite,
                            transparent: c.material.transparent
                        }
                    });
                } else if (c.isLineSegments || c.isLine) {
                    const posAttr = c.geometry.attributes.position;
                    const vertCount = posAttr ? posAttr.count : 0;
                    const verts = [];
                    if (posAttr) {
                        for (let i = 0; i < vertCount; i++) {
                            verts.push({
                                x: posAttr.getX(i),
                                y: posAttr.getY(i),
                                z: posAttr.getZ(i)
                            });
                        }
                    }
                    lineSegments.push({
                        index: idx,
                        type: c.type,
                        vertexCount: vertCount,
                        segmentCount: vertCount / 2,
                        vertices: verts,
                        visible: c.visible,
                        renderOrder: c.renderOrder
                    });
                } else {
                    unknown.push({ index: idx, type: c.type });
                }
            });

            return {
                totalChildren: grp.children.length,
                spriteCount: sprites.length,
                lineSegmentsCount: lineSegments.length,
                unknownCount: unknown.length,
                sprites: sprites,
                lineSegments: lineSegments
            };
        }''')

        # 2. Geometric Verification of Endpoint Distances
        geom_verification = await page.evaluate('''() => {
            const grp = window.groupDimensions;
            const results = [];

            // Helper to compute distance between two 3D points
            function dist3D(p1, p2) {
                const dx = p2.x - p1.x;
                const dy = p2.y - p1.y;
                const dz = p2.z - p1.z;
                return Math.sqrt(dx * dx + dy * dy + dz * dz);
            }

            // Inspect line segments that correspond to linear dimensions
            // addLinearDimension3D generates 5 segments:
            // extA (v0-v1), extB (v2-v3), dimLine (v4-v5), tickA (v6-v7), tickB (v8-v9)
            grp.children.forEach((c, idx) => {
                if (c.isLineSegments) {
                    const pos = c.geometry.attributes.position;
                    if (pos.count === 10) {
                        // Standard linear dimension line
                        const qA = { x: pos.getX(4), y: pos.getY(4), z: pos.getZ(4) };
                        const qB = { x: pos.getX(5), y: pos.getY(5), z: pos.getZ(5) };
                        const length = dist3D(qA, qB);

                        // Find corresponding sprite adjacent in group
                        const nextSibling = grp.children[idx + 1];
                        const label = (nextSibling && nextSibling.isSprite && nextSibling.userData) ? nextSibling.userData.label : '';

                        results.push({
                            type: 'linear_dimension',
                            childIndex: idx,
                            qA, qB,
                            measuredLength: length,
                            label: label
                        });
                    } else if (pos.count === 2) {
                        // Single indicator segment (e.g., expansion joints)
                        const p0 = { x: pos.getX(0), y: pos.getY(0), z: pos.getZ(0) };
                        const p1 = { x: pos.getX(1), y: pos.getY(1), z: pos.getZ(1) };
                        const length = dist3D(p0, p1);
                        const nextSibling = grp.children[idx + 1];
                        const label = (nextSibling && nextSibling.isSprite && nextSibling.userData) ? nextSibling.userData.label : '';

                        results.push({
                            type: 'joint_indicator',
                            childIndex: idx,
                            p0, p1,
                            measuredLength: length,
                            label: label
                        });
                    } else if (pos.count > 10) {
                        // Alignment polyline dimension
                        // First (N - 6) vertices are polyline segments (each segment = 2 vertices)
                        // Then 2 extension drops (4 vertices) + 2 ticks (4 vertices) = 8 vertices or similar
                        // Let's compute polyline cumulative length
                        // In addAlignmentSegmentDimension:
                        // for (let i = 0; i < pts.length - 1; i++) addSeg(pts[i], pts[i+1]);
                        // followed by start drop, end drop, tickStart, tickEnd (total 4 extra segments = 8 verts)
                        const polySegsCount = (pos.count - 8) / 2;
                        let cumLen = 0;
                        for (let s = 0; s < polySegsCount; s++) {
                            const v1 = { x: pos.getX(s * 2), y: pos.getY(s * 2), z: pos.getZ(s * 2) };
                            const v2 = { x: pos.getX(s * 2 + 1), y: pos.getY(s * 2 + 1), z: pos.getZ(s * 2 + 1) };
                            cumLen += dist3D(v1, v2);
                        }

                        const nextSibling = grp.children[idx + 1];
                        const label = (nextSibling && nextSibling.isSprite && nextSibling.userData) ? nextSibling.userData.label : '';

                        results.push({
                            type: 'alignment_dimension',
                            childIndex: idx,
                            segmentCount: polySegsCount,
                            totalVertexCount: pos.count,
                            measuredArcLength: cumLen,
                            label: label
                        });
                    }
                }
            });

            return results;
        }''')

        # 3. Test WebGL canvas captureStream(30) with all 21 sprites
        capture_stream_test = await page.evaluate('''() => {
            const canvas = document.querySelector('canvas');
            if (!canvas) return { error: 'WebGL canvas not found' };

            // Verify captureStream method
            if (typeof canvas.captureStream !== 'function') {
                return { error: 'canvas.captureStream is not supported' };
            }

            const stream = canvas.captureStream(30);
            const videoTracks = stream.getVideoTracks();
            if (!videoTracks || videoTracks.length === 0) {
                return { error: 'No video tracks generated by captureStream' };
            }

            const track = videoTracks[0];
            const settings = track.getSettings ? track.getSettings() : {};

            // Verify that all 21 sprites are actively rendered in the scene graph during capture
            const grp = window.groupDimensions;
            let activeSpritesCount = 0;
            let renderedSpritesInFrustum = 0;

            // Trigger frame render
            renderer.render(scene, camera);

            // Check frustum inclusion for camera
            camera.updateMatrixWorld();
            camera.matrixWorldInverse.copy(camera.matrixWorld).invert();
            const frustum = new THREE.Frustum();
            frustum.setFromProjectionMatrix(
                new THREE.Matrix4().multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse)
            );

            grp.children.forEach(c => {
                if (c.isSprite && c.visible && grp.visible) {
                    activeSpritesCount++;
                    // Check if sprite center is inside frustum
                    if (frustum.containsPoint(c.position)) {
                        renderedSpritesInFrustum++;
                    }
                }
            });

            // Clean up track
            track.stop();

            return {
                captureStreamSupported: true,
                trackKind: track.kind,
                trackEnabled: track.enabled,
                trackReadyState: track.readyState,
                activeSpritesCount: activeSpritesCount,
                renderedSpritesInFrustum: renderedSpritesInFrustum,
                canvasWidth: canvas.width,
                canvasHeight: canvas.height
            };
        }''')

        # 4. Measure Real FPS over 120 animation frames
        fps_benchmark = await page.evaluate('''() => {
            return new Promise((resolve) => {
                let frameCount = 0;
                let startTime = performance.now();
                const deltas = [];
                let lastTime = startTime;

                function tick(now) {
                    frameCount++;
                    deltas.push(now - lastTime);
                    lastTime = now;

                    if (frameCount >= 120) {
                        const totalTime = now - startTime;
                        const avgFps = (frameCount / totalTime) * 1000;
                        const avgDelta = totalTime / frameCount;
                        deltas.sort((a, b) => a - b);
                        const p95Delta = deltas[Math.floor(deltas.length * 0.95)];
                        const minFps = 1000 / deltas[deltas.length - 1]; // worst frame
                        resolve({
                            frames: frameCount,
                            totalTimeMs: totalTime,
                            avgFps: avgFps,
                            minFps: minFps,
                            avgDeltaMs: avgDelta,
                            p95DeltaMs: p95Delta
                        });
                    } else {
                        requestAnimationFrame(tick);
                    }
                }
                requestAnimationFrame(tick);
            });
        }''')

        await browser.close()

        # Format output
        output_report = {
            "consoleErrors": console_errors,
            "sceneInspection": scene_inspection,
            "geomVerification": geom_verification,
            "captureStreamTest": capture_stream_test,
            "fpsBenchmark": fps_benchmark
        }

        print("\n--- SCENE GRAPH CHILD COUNTS ---")
        print(f"Total Children in groupDimensions: {scene_inspection.get('totalChildren')}")
        print(f"Sprites: {scene_inspection.get('spriteCount')} (Target: 21)")
        print(f"LineSegments: {scene_inspection.get('lineSegmentsCount')} (Target: 20)")
        print(f"Unknown Objects: {scene_inspection.get('unknownCount')} (Target: 0)")

        print("\n--- GEOMETRIC DISTANCE VERIFICATION ---")
        for item in geom_verification:
            lbl = item.get('label')
            if item.get('type') == 'linear_dimension':
                measured = item.get('measuredLength')
                print(f"Linear Dim [Index {item.get('childIndex')}]: Measured = {measured:.4f} m | Label = '{lbl}'")
            elif item.get('type') == 'alignment_dimension':
                arc_len = item.get('measuredArcLength')
                print(f"Alignment Dim [Index {item.get('childIndex')}]: ArcLength = {arc_len:.4f} m | Label = '{lbl}'")
            elif item.get('type') == 'joint_indicator':
                measured = item.get('measuredLength')
                print(f"Joint Indicator [Index {item.get('childIndex')}]: Height = {measured:.4f} m | Label = '{lbl}'")

        print("\n--- WEBGL CANVAS CAPTURESTREAM TEST ---")
        print(json.dumps(capture_stream_test, indent=2))

        print("\n--- FPS BENCHMARK ---")
        print(json.dumps(fps_benchmark, indent=2))

        # Assertions
        assert len(console_errors) == 0, f"Console errors found: {console_errors}"
        assert scene_inspection['spriteCount'] == 21, f"Expected exactly 21 sprites, found {scene_inspection['spriteCount']}"
        assert scene_inspection['lineSegmentsCount'] == 20, f"Expected exactly 20 lineSegments, found {scene_inspection['lineSegmentsCount']}"
        assert scene_inspection['unknownCount'] == 0, f"Expected 0 unknown objects, found {scene_inspection['unknownCount']}"
        assert capture_stream_test['captureStreamSupported'] is True
        assert capture_stream_test['activeSpritesCount'] == 21
        assert fps_benchmark['avgFps'] > 30.0, f"Average FPS {fps_benchmark['avgFps']} is below 30 FPS"

        print("\n>>> ALL ADVERSARIAL ASSERTIONS PASSED WITH ZERO REGRESSIONS! <<<")
        return output_report

if __name__ == '__main__':
    asyncio.run(run_adversarial_verification())
