#!/usr/bin/env python3
"""
DSS Admin UI - Phase 1: Smoke Test (Component Loading)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Framework: Pytest-Playwright (Python-based)
Purpose: Validate all 51 components load successfully with zero console errors
Coverage: Component loading, DOM rendering, error detection

Test Strategy:
  1. Navigate to component
  2. Wait for load (max 3s)
  3. Check console logs (zero errors expected)
  4. Validate DOM rendering (not blank)
  5. Record result

Generated: 2025-12-08
Author: Gemini 3 Pro Expert Analysis
Status: Ready for Implementation
"""

import pytest
import json
import time
from pathlib import Path
from playwright.sync_api import sync_playwright, expect
import subprocess
import re

# Component registry from component-registry.js
COMPONENTS = {
    # Tools components
    'ds-metrics-panel': 'Tools',
    'ds-console-viewer': 'Tools',
    'ds-token-inspector': 'Tools',
    'ds-figma-status': 'Tools',
    'ds-activity-log': 'Tools',
    'ds-visual-diff': 'Tools',
    'ds-accessibility-report': 'Tools',
    'ds-screenshot-gallery': 'Tools',
    'ds-network-monitor': 'Tools',
    'ds-test-results': 'Tools',
    'ds-system-log': 'Tools',
    'ds-figma-extract-quick': 'Tools',
    'ds-quick-wins-script': 'Tools',
    'ds-regression-testing': 'Tools',
    'ds-storybook-figma-compare': 'Tools',
    'ds-storybook-live-compare': 'Tools',
    'ds-figma-extraction': 'Tools',
    'ds-project-analysis': 'Tools',
    'ds-figma-plugin': 'Tools',
    'ds-token-list': 'Tools',
    'ds-asset-list': 'Tools',
    'ds-component-list': 'Tools',
    'ds-navigation-demos': 'Tools',
    'ds-figma-live-compare': 'Tools',
    'ds-esre-editor': 'Tools',
    'ds-chat-panel': 'Tools',
    'ds-quick-wins': 'Tools',

    # Layout components
    'ds-shell': 'Layout',
    'ds-panel': 'Layout',
    'ds-activity-bar': 'Layout',
    'ds-ai-chat-sidebar': 'Layout',
    'ds-project-selector': 'Layout',

    # Metrics components
    'ds-frontpage': 'Metrics',
    'ds-metric-card': 'Metrics',
    'ds-metrics-dashboard': 'Metrics',

    # Admin components
    'ds-admin-settings': 'Admin',
    'ds-project-list': 'Admin',
    'ds-user-settings': 'Admin',

    # UI Elements
    'ds-action-bar': 'UI',
    'ds-badge': 'UI',
    'ds-base-tool': 'Base',
    'ds-button': 'UI',
    'ds-card': 'UI',
    'ds-component-base': 'Base',
    'ds-input': 'UI',
    'ds-notification-center': 'UI',
    'ds-toast': 'UI',
    'ds-toast-provider': 'UI',
    'ds-workflow': 'UI',

    # Listing components
    'ds-icon-list': 'Listings',
    'ds-jira-issues': 'Listings',
}

BASE_URL = "http://localhost:5173"  # Vite dev server
TIMEOUT = 3000  # 3 seconds for component load


class ConsoleLogCapture:
    """Capture and analyze console logs during test"""
    def __init__(self):
        self.logs = []
        self.errors = []
        self.warnings = []

    def handle_console_message(self, msg):
        """Handle console events from browser"""
        entry = {
            'type': msg.type,
            'text': msg.text,
            'timestamp': time.time()
        }
        self.logs.append(entry)

        if msg.type == 'error':
            self.errors.append(msg.text)
        elif msg.type == 'warning':
            self.warnings.append(msg.text)

    def has_critical_errors(self):
        """Check for critical errors (not warnings)"""
        critical_patterns = [
            r"Cannot read properties of undefined",
            r"Unknown component:",
            r"Failed to hydrate",
            r"Uncaught.*Error",
            r"fetch failed",
            r"CORS",
        ]
        for error in self.errors:
            for pattern in critical_patterns:
                if re.search(pattern, error, re.IGNORECASE):
                    return True, error
        return False, None

    def get_summary(self):
        """Get log summary statistics"""
        return {
            'total_logs': len(self.logs),
            'errors': len(self.errors),
            'warnings': len(self.warnings),
            'critical_errors': self.has_critical_errors()[0]
        }


@pytest.fixture(scope="session")
def browser_context():
    """Start Playwright browser session"""
    with sync_playwright() as p:
        browser = p.chromium.launch()
        context = browser.new_context()
        yield context
        context.close()
        browser.close()


@pytest.fixture
def page(browser_context):
    """Create new page for each test"""
    page = browser_context.new_page()
    yield page
    page.close()


def test_admin_page_loads(page):
    """Test 1: Verify admin page loads without crashing"""
    page.goto(f"{BASE_URL}/")
    page.wait_for_load_state("networkidle")

    # Check for shell component presence
    shell = page.query_selector("ds-shell")
    assert shell is not None, "Main shell component not found on page"


@pytest.mark.parametrize("component_tag,category", [
    (tag, cat) for tag, cat in COMPONENTS.items()
])
def test_component_loads(page, component_tag, category):
    """
    Test 2: Verify each component loads successfully

    Runs for all 51 components:
    - Navigate to page
    - Trigger component load
    - Check for errors
    - Verify DOM rendering
    """

    console_capture = ConsoleLogCapture()
    page.on("console", console_capture.handle_console_message)

    # Navigate to admin page
    page.goto(f"{BASE_URL}/")
    page.wait_for_load_state("networkidle")

    # Attempt to load component via registry
    try:
        # Check if component is in registry
        registry_check = page.evaluate(f"""
            () => {{
                const registry = window.__COMPONENT_REGISTRY__;
                return registry && '{component_tag}' in registry;
            }}
        """)

        if not registry_check:
            pytest.skip(f"Component {component_tag} not in registry")

        # Hydrate component
        result = page.evaluate(f"""
            async () => {{
                try {{
                    const {{ hydrateComponent }} = await import('../js/config/component-registry.js');
                    const container = document.createElement('div');
                    document.body.appendChild(container);
                    const element = await hydrateComponent('{component_tag}', container);
                    return {{
                        success: true,
                        tagName: element?.tagName?.toLowerCase(),
                        html: element?.outerHTML?.substring(0, 100)
                    }};
                }} catch (err) {{
                    return {{
                        success: false,
                        error: err.message
                    }};
                }}
            }}
        """)

        # Wait for component to render
        page.wait_for_timeout(500)

        # Check results
        has_errors, error_msg = console_capture.has_critical_errors()

        assert result['success'], f"Failed to load {component_tag}: {result.get('error')}"
        assert not has_errors, f"Critical error in {component_tag}: {error_msg}"

        # Verify DOM element exists
        element = page.query_selector(component_tag)
        assert element is not None, f"Component {component_tag} not found in DOM after load"

    except Exception as e:
        pytest.fail(f"Test failed for {component_tag}: {str(e)}")

    finally:
        # Log summary
        summary = console_capture.get_summary()
        print(f"\n[{category}] {component_tag}")
        print(f"  Logs: {summary['total_logs']}, Errors: {summary['errors']}, Warnings: {summary['warnings']}")


def test_component_dom_rendering(page, component_tag='ds-shell', category='Layout'):
    """
    Test 3: Verify component DOM rendering

    Checks that component properly renders with visible content
    """
    console_capture = ConsoleLogCapture()
    page.on("console", console_capture.handle_console_message)

    page.goto(f"{BASE_URL}/")
    page.wait_for_load_state("networkidle")

    # Check that component is in DOM
    element = page.query_selector(component_tag)
    assert element is not None, f"{component_tag} not found in initial DOM"

    # Check that element has content (not completely blank)
    content = page.evaluate(f"""
        () => {{
            const el = document.querySelector('{component_tag}');
            return {{
                innerHTML: el?.innerHTML?.length > 0,
                textContent: el?.textContent?.trim().length > 0,
                childNodes: el?.childNodes?.length || 0,
                hasAttributes: el?.attributes?.length > 0
            }};
        }}
    """)

    assert content['innerHTML'] or content['childNodes'] > 0, f"{component_tag} has no rendered content"


def test_no_uncaught_errors(page):
    """
    Test 4: Verify no uncaught errors during page lifecycle

    Records all console errors and validates clean execution
    """

    captured_errors = []

    def handle_error(msg):
        if msg.type == 'error':
            captured_errors.append(msg.text)

    page.on("console", handle_error)
    page.on("pageerror", lambda err: captured_errors.append(f"Uncaught: {str(err)}"))

    page.goto(f"{BASE_URL}/")
    page.wait_for_load_state("networkidle")

    # Allow brief time for deferred errors
    page.wait_for_timeout(1000)

    # Filter out non-critical messages
    critical_errors = [
        e for e in captured_errors
        if not any(x in e for x in ['Deprecated', 'Warning', 'chrome-extension'])
    ]

    assert len(critical_errors) == 0, f"Found {len(critical_errors)} critical errors: {critical_errors[:5]}"


def test_api_endpoints_available(page):
    """
    Test 5: Verify API endpoints are available

    Tests connectivity to backend API
    """

    page.goto(f"{BASE_URL}/")
    page.wait_for_load_state("networkidle")

    # Test key API endpoints
    endpoints = [
        "/api/projects",
        "/api/auth/me",
        "/api/mcp/tools",
    ]

    for endpoint in endpoints:
        try:
            response = page.evaluate(f"""
                async () => {{
                    try {{
                        const resp = await fetch('{endpoint}');
                        return {{
                            status: resp.status,
                            ok: resp.ok
                        }};
                    }} catch (err) {{
                        return {{ error: err.message }};
                    }}
                }}
            """)

            # We expect either success or a specific error (not network failure)
            assert 'error' not in response or 'fetch' not in response.get('error', ''), \
                f"Network error accessing {endpoint}: {response.get('error')}"

        except Exception as e:
            pytest.skip(f"Could not test {endpoint}: {str(e)}")


# ────────────────────────────────────────────────────────────────────────────
# Test Execution & Reporting
# ────────────────────────────────────────────────────────────────────────────

@pytest.fixture(scope="session", autouse=True)
def print_test_header():
    """Print header with test information"""
    print("\n" + "="*80)
    print("DSS Admin UI - Phase 1: Smoke Test (Component Loading)")
    print("="*80)
    print(f"Framework: Pytest-Playwright")
    print(f"Components: {len(COMPONENTS)} registered components")
    print(f"Base URL: {BASE_URL}")
    print(f"Timeout: {TIMEOUT}ms per component")
    print("="*80)


def pytest_runtest_makereport(item, call):
    """Hook to capture test results"""
    if call.when == "call":
        if "component_tag" in item.callspec.params:
            component = item.callspec.params['component_tag']
            category = item.callspec.params.get('category', 'Unknown')
            status = "✅ PASS" if item.outcomes == [] else "❌ FAIL"
            print(f"  {status} [{category}] {component}")


if __name__ == "__main__":
    print(f"""
    ╔═══════════════════════════════════════════════════════════════════════════╗
    ║                                                                           ║
    ║        DSS Admin UI - Phase 1 Smoke Test                                 ║
    ║        pytest-playwright Component Loading Validation                     ║
    ║                                                                           ║
    ║        To run tests:                                                      ║
    ║        $ pytest .dss/test_smoke_phase1.py -v                             ║
    ║                                                                           ║
    ║        To run specific component:                                         ║
    ║        $ pytest .dss/test_smoke_phase1.py -k ds-shell -v                ║
    ║                                                                           ║
    ║        To run with coverage:                                              ║
    ║        $ pytest .dss/test_smoke_phase1.py --cov=admin-ui -v              ║
    ║                                                                           ║
    ║        To run with headless browser (CI/CD):                              ║
    ║        $ pytest .dss/test_smoke_phase1.py --headless -v                  ║
    ║                                                                           ║
    ╚═══════════════════════════════════════════════════════════════════════════╝
    """)
