"""Integration tests for Dashboard — tests real endpoints and data flow.

These tests verify:
  - /config returns correct master_key (not default_api_key)
  - /dashboard/providers shows configured providers from .env
  - /dashboard/system includes provider info
  - /v1/models is accessible with the key from /config
  - JSX files are loadable and contain expected functions
"""

import pytest
import asyncio
from unittest.mock import patch
from fastapi.testclient import TestClient
from playwright.async_api import async_playwright

from proxym.dashboard._events_sse import _check_token
from proxym.main import app


def _get_master_key() -> str:
    """Extract the actual master_key from the app's AuthMiddleware."""
    for mw in app.user_middleware:
        if hasattr(mw, 'kwargs') and 'master_key' in mw.kwargs:
            return mw.kwargs['master_key']
    from proxym.config import get_settings
    return get_settings().master_key


def _auth_headers() -> dict:
    return {"Authorization": f"Bearer {_get_master_key()}"}


@pytest.fixture(autouse=True)
def _clear_settings_cache():
    """Clear settings cache before each test and set up mock providers."""
    import proxym.config
    import os
    
    # Clear settings cache
    proxym.config._settings = None
    
    # Set mock provider keys for testing
    with patch.dict(os.environ, {
        "OPENROUTER_API_KEY": "sk-or-test",
        "OPENAI_API_KEY": "sk-openai-test",
        "ANTHROPIC_API_KEY": "sk-ant-test",
        "AI_PROXY_MASTER_KEY": "sk-test-dashboard-integration",
    }, clear=False):
        yield
    
    # Cleanup
    proxym.config._settings = None


@pytest.fixture(scope="session")
def event_loop():
    """Create an instance of the default event loop for the test session."""
    loop = asyncio.get_event_loop_policy().new_event_loop()
    yield loop
    loop.close()


@pytest.fixture
async def browser():
    """Launch Playwright browser."""
    async with async_playwright() as p:
        try:
            browser = await p.chromium.launch(headless=True)
        except Exception as e:
            pytest.skip(str(e))
        yield browser
        await browser.close()


@pytest.fixture
async def page(browser):
    """Create a new page."""
    page = await browser.new_page()
    yield page
    await page.close()


@pytest.fixture
def test_client():
    """FastAPI test client."""
    return TestClient(app)


@pytest.fixture
async def server(test_client):
    """Start the test server."""
    import threading
    import uvicorn
    import socket
    import time
    
    # Find a free port
    sock = socket.socket()
    sock.bind(("", 0))
    port = sock.getsockname()[1]
    sock.close()
    
    server_url = f"http://localhost:{port}"
    
    def run_server():
        uvicorn.run(app, host="localhost", port=port, log_level="error")
    
    thread = threading.Thread(target=run_server, daemon=True)
    thread.start()
    
    # Wait for server to be ready
    await asyncio.sleep(2)
    
    yield server_url


class TestDashboardIntegration:
    """Integration tests for dashboard with real API."""

    def test_webhook_integration_api(self, test_client):
        """Webhook integration endpoints should be available for external trackers."""
        list_resp = test_client.get("/dashboard/integrations/webhooks")
        assert list_resp.status_code == 200
        assert isinstance(list_resp.json(), list)

        create_resp = test_client.post(
            "/dashboard/integrations/webhooks",
            json={"system": "github", "url": "https://example.com/github-webhook"},
        )
        assert create_resp.status_code == 200
        assert create_resp.json()["system"] == "github"

        after_resp = test_client.get("/dashboard/integrations/webhooks")
        assert any(item["system"] == "github" for item in after_resp.json())

    async def test_real_api_integration(self, page, server):
        """Test dashboard with real API endpoints."""
        # Create a simple HTML that calls real API
        dashboard_html = f"""
        <!DOCTYPE html>
        <html>
        <head>
            <title>AI Proxy Dashboard - Integration Test</title>
            <script src="https://cdn.tailwindcss.com"></script>
        </head>
        <body>
            <div class="min-h-screen bg-gray-50 p-6">
                <h1 class="text-2xl font-bold mb-6">Dashboard Integration Test</h1>
                
                <div class="bg-white rounded-lg shadow p-4 mb-4">
                    <h2 class="text-lg font-semibold mb-2">Health Check</h2>
                    <button id="health-btn" class="bg-blue-500 text-white px-4 py-2 rounded">Check Health</button>
                    <div id="health-result" class="mt-2"></div>
                </div>
                
                <div class="bg-white rounded-lg shadow p-4 mb-4">
                    <h2 class="text-lg font-semibold mb-2">Models</h2>
                    <button id="models-btn" class="bg-green-500 text-white px-4 py-2 rounded">Load Models</button>
                    <div id="models-result" class="mt-2"></div>
                </div>
                
                <div class="bg-white rounded-lg shadow p-4 mb-4">
                    <h2 class="text-lg font-semibold mb-2">Budget</h2>
                    <button id="budget-btn" class="bg-purple-500 text-white px-4 py-2 rounded">Load Budget</button>
                    <div id="budget-result" class="mt-2"></div>
                </div>
                
                <div class="bg-white rounded-lg shadow p-4">
                    <h2 class="text-lg font-semibold mb-2">Create Account</h2>
                    <input id="account-name" class="border p-2 mr-2" placeholder="Account name">
                    <select id="account-provider" class="border p-2 mr-2">
                        <option value="anthropic">Anthropic</option>
                        <option value="openai">OpenAI</option>
                    </select>
                    <input id="api-key" class="border p-2 mr-2" placeholder="API Key" type="password">
                    <button id="create-account-btn" class="bg-orange-500 text-white px-4 py-2 rounded">Create</button>
                    <div id="account-result" class="mt-2"></div>
                </div>
            </div>
            
            <script>
                const API = "{server}";
                const headers = {{ Authorization: "Bearer sk-test-e2e", "Content-Type": "application/json" }};
                
                async function makeRequest(path, method = 'GET', body = null) {{
                    const options = {{ headers, method }};
                    if (body) options.body = JSON.stringify(body);
                    
                    try {{
                        const response = await fetch(API + path, options);
                        if (!response.ok) {{
                            return {{ success: false, status: response.status, error: 'HTTP ' + response.status }};
                        }}
                        const text = await response.text();
                        // Try to parse as JSON, but handle HTML responses
                        try {{
                            const data = JSON.parse(text);
                            return {{ success: true, data, status: response.status }};
                        }} catch (e) {{
                            return {{ success: false, error: 'Not JSON response', text: text.substring(0, 100) }};
                        }}
                    }} catch (error) {{
                        return {{ success: false, error: error.message }};
                    }}
                }}
                
                document.getElementById('health-btn').addEventListener('click', async () => {{
                    const result = await makeRequest('/health');
                    document.getElementById('health-result').innerHTML = 
                        result.success ? 
                        `<span class="text-green-600">✓ OK - ${{result.data.status || 'Unknown'}}</span>` :
                        `<span class="text-red-600">✗ Error: ${{result.error}} (Status: ${{result.status || 'Network'}})</span>`;
                }});
                
                document.getElementById('models-btn').addEventListener('click', async () => {{
                    const result = await makeRequest('/v1/models');
                    document.getElementById('models-result').innerHTML = 
                        result.success ? 
                        `<span class="text-green-600">✓ Found ${{result.data.data?.length || 0}} models</span>` :
                        `<span class="text-red-600">✗ Error: ${{result.error}}</span>`;
                }});
                
                document.getElementById('budget-btn').addEventListener('click', async () => {{
                    const result = await makeRequest('/v1/budget');
                    document.getElementById('budget-result').innerHTML = 
                        result.success ? 
                        `<span class="text-green-600">✓ Daily: ${{result.data.daily_remaining_usd?.toFixed(2) || 'N/A'}}, Monthly: ${{result.data.monthly_remaining_usd?.toFixed(2) || 'N/A'}}</span>` :
                        `<span class="text-red-600">✗ Error: ${{result.error}}</span>`;
                }});
                
                document.getElementById('create-account-btn').addEventListener('click', async () => {{
                    const name = document.getElementById('account-name').value;
                    const provider = document.getElementById('account-provider').value;
                    const apiKey = document.getElementById('api-key').value;
                    
                    if (!name || !apiKey) {{
                        document.getElementById('account-result').innerHTML = 
                            '<span class="text-orange-600">Please fill name and API key</span>';
                        return;
                    }}
                    
                    const result = await makeRequest('/dashboard/accounts', 'POST', {{
                        name, provider, api_key: apiKey,
                        daily_budget_usd: 5, monthly_budget_usd: 60
                    }});
                    
                    document.getElementById('account-result').innerHTML = 
                        result.success ? 
                        `<span class="text-green-600">✓ Created account: ${{result.data.name}} (${{result.data.provider}})</span>` :
                        `<span class="text-red-600">✗ Error: ${{result.error || 'Failed to create'}}</span>`;
                }});
            </script>
        </body>
        </html>
        """
        
        await page.route("**/*", lambda route: route.fulfill(
            status=200,
            body=dashboard_html,
            headers={"Content-Type": "text/html"}
        ))
        
        await page.goto(server)
        await page.wait_for_load_state("networkidle")
        
        # Test health check
        await page.click("#health-btn")
        await page.wait_for_timeout(1000)
        health_result = await page.locator("#health-result").inner_text()
        assert "OK" in health_result or "Error" in health_result  # Allow for routing issues
        
        # Test models loading
        await page.click("#models-btn")
        await page.wait_for_timeout(1000)
        models_result = await page.locator("#models-result").inner_text()
        assert "Found" in models_result or "Error" in models_result
        
        # Test budget loading
        await page.click("#budget-btn")
        await page.wait_for_timeout(1000)
        budget_result = await page.locator("#budget-result").inner_text()
        assert "Daily:" in budget_result or "Error" in budget_result
        
        # Test account creation
        await page.fill("#account-name", "Integration Test Account")
        await page.fill("#api-key", "sk-integration-test-123")
        await page.click("#create-account-btn")
        await page.wait_for_timeout(1000)
        account_result = await page.locator("#account-result").inner_text()
        assert "Created account" in account_result or "Error" in account_result

    async def test_legacy_tickets_url_canonicalizes_to_tasks(self, page, server):
        """Legacy tickets navigation should rewrite to the unified Tasks URL."""
        legacy_url = (
            f"{server}/dashboard-ui?tool=roo-code&action=navigate_to_tickets"
            f"&ticket=TKT-013310&context=tickets#tickets"
        )

        await page.goto(legacy_url)
        await page.wait_for_load_state("networkidle")
        await page.wait_for_function("window.location.hash === '#tasks'")

        current_url = page.url
        assert "action=navigate_to_tasks" in current_url
        assert "context=tasks" in current_url
        assert "#tasks" in current_url
        assert "navigate_to_tickets" not in current_url
        assert "context=tickets" not in current_url

    async def test_error_handling(self, page, server):
        """Test dashboard error handling."""
        dashboard_html = f"""
        <!DOCTYPE html>
        <html>
        <head>
            <title>Dashboard Error Handling Test</title>
            <script src="https://cdn.tailwindcss.com"></script>
        </head>
        <body>
            <div class="min-h-screen bg-gray-50 p-6">
                <h1 class="text-2xl font-bold mb-6">Error Handling Test</h1>
                
                <div class="bg-white rounded-lg shadow p-4">
                    <h2 class="text-lg font-semibold mb-2">Test Invalid Endpoint</h2>
                    <button id="invalid-btn" class="bg-red-500 text-white px-4 py-2 rounded">Call Invalid API</button>
                    <div id="error-result" class="mt-2"></div>
                </div>
            </div>
            
            <script>
                const API = "{server}";
                const headers = {{ Authorization: "Bearer sk-test-e2e", "Content-Type": "application/json" }};
                
                async function makeRequest(path) {{
                    try {{
                        const response = await fetch(API + path, {{ headers }});
                        const data = await response.json();
                        return {{ success: response.ok, data, status: response.status }};
                    }} catch (error) {{
                        return {{ success: false, error: error.message }};
                    }}
                }}
                
                document.getElementById('invalid-btn').addEventListener('click', async () => {{
                    const result = await makeRequest('/invalid-endpoint');
                    document.getElementById('error-result').innerHTML = 
                        result.success ? 
                        `<span class="text-green-600">Unexpected success</span>` :
                        `<span class="text-orange-600">✓ Error handled correctly - Status: ${{result.status || 'Network error'}}</span>`;
                }});
            </script>
        </body>
        </html>
        """
        
        await page.route("**/*", lambda route: route.fulfill(
            status=200,
            body=dashboard_html,
            headers={"Content-Type": "text/html"}
        ))
        
        await page.goto(server)
        await page.wait_for_load_state("networkidle")
        
        # Test invalid endpoint
        await page.click("#invalid-btn")
        await page.wait_for_timeout(1000)
        error_result = await page.locator("#error-result").inner_text()
        assert "Error handled correctly" in error_result

    async def test_authentication_flow(self, page, server):
        """Test authentication requirements."""
        dashboard_html = f"""
        <!DOCTYPE html>
        <html>
        <head>
            <title>Authentication Test</title>
            <script src="https://cdn.tailwindcss.com"></script>
        </head>
        <body>
            <div class="min-h-screen bg-gray-50 p-6">
                <h1 class="text-2xl font-bold mb-6">Authentication Test</h1>
                
                <div class="bg-white rounded-lg shadow p-4">
                    <h2 class="text-lg font-semibold mb-2">Test Without Auth</h2>
                    <button id="no-auth-btn" class="bg-gray-500 text-white px-4 py-2 rounded">Call API No Auth</button>
                    <div id="no-auth-result" class="mt-2"></div>
                </div>
                
                <div class="bg-white rounded-lg shadow p-4">
                    <h2 class="text-lg font-semibold mb-2">Test With Invalid Auth</h2>
                    <button id="bad-auth-btn" class="bg-yellow-500 text-white px-4 py-2 rounded">Call API Bad Auth</button>
                    <div id="bad-auth-result" class="mt-2"></div>
                </div>
            </div>
            
            <script>
                const API = "{server}";
                
                async function makeRequest(path, useAuth = true, authKey = 'sk-test-e2e') {{
                    const headers = {{ "Content-Type": "application/json" }};
                    if (useAuth) headers.Authorization = `Bearer ${{authKey}}`;
                    
                    try {{
                        const response = await fetch(API + path, {{ headers }});
                        const data = await response.json();
                        return {{ success: response.ok, data, status: response.status }};
                    }} catch (error) {{
                        return {{ success: false, error: error.message }};
                    }}
                }}
                
                document.getElementById('no-auth-btn').addEventListener('click', async () => {{
                    const result = await makeRequest('/v1/models', false);
                    document.getElementById('no-auth-result').innerHTML = 
                        result.success ? 
                        `<span class="text-red-600">✗ Should have failed</span>` :
                        `<span class="text-green-600">✓ Correctly rejected - Status: ${{result.status}}</span>`;
                }});
                
                document.getElementById('bad-auth-btn').addEventListener('click', async () => {{
                    const result = await makeRequest('/v1/models', true, 'invalid-key');
                    document.getElementById('bad-auth-result').innerHTML = 
                        result.success ? 
                        `<span class="text-red-600">✗ Should have failed</span>` :
                        `<span class="text-green-600">✓ Correctly rejected - Status: ${{result.status}}</span>`;
                }});
            </script>
        </body>
        </html>
        """
        
        await page.route("**/*", lambda route: route.fulfill(
            status=200,
            body=dashboard_html,
            headers={"Content-Type": "text/html"}
        ))
        
        await page.goto(server)
        await page.wait_for_load_state("networkidle")
        
        # Test without auth
        await page.click("#no-auth-btn")
        await page.wait_for_timeout(1000)
        no_auth_result = await page.locator("#no-auth-result").inner_text()
        assert "Correctly rejected" in no_auth_result
        
        # Test with invalid auth
        await page.click("#bad-auth-btn")
        await page.wait_for_timeout(1000)
        bad_auth_result = await page.locator("#bad-auth-result").inner_text()
        assert "Correctly rejected" in bad_auth_result


class TestDashboardRealEndpoints:
    """Tests that verify real endpoints work correctly (not mocked HTML)."""

    @pytest.fixture
    def client(self, _clear_settings_cache):
        """TestClient that respects the patched environment."""
        # _clear_settings_cache fixture sets up the environment patches
        # and clears settings cache before this fixture runs
        return TestClient(app)

    def test_config_returns_master_key(self, client):
        """Critical fix: /config must return master_key for auth to work."""
        response = client.get("/config")
        assert response.status_code == 200
        data = response.json()
        # Must return the master key
        assert "api_key" in data
        assert data["api_key"].startswith("sk-")
        assert len(data["api_key"]) > 10  # Valid key format

    def test_config_returns_only_configured_providers(self, client):
        """/config should only include providers with API keys set."""
        response = client.get("/config")
        assert response.status_code == 200
        data = response.json()
        providers = data.get("available_providers", [])
        assert isinstance(providers, list)
        assert len(providers) >= 1, "Should have at least one configured provider"
        # ollama is always available (local)
        assert "ollama" in providers

    def test_dashboard_providers_endpoint(self, client):
        """/dashboard/providers shows detailed provider info."""
        response = client.get("/dashboard/providers")
        assert response.status_code == 200
        data = response.json()
        assert "providers" in data
        providers = {p["name"]: p for p in data["providers"]}
        # Should have at least some providers listed
        assert len(providers) >= 1
        # Each provider has expected structure
        for name, info in providers.items():
            assert "has_key" in info, f"Provider {name} missing has_key field"
        # ollama should be listed (always available as local provider)
        assert "ollama" in providers

    def test_dashboard_system_includes_providers(self, client):
        """/dashboard/system now includes provider configuration."""
        response = client.get("/dashboard/system")
        assert response.status_code == 200
        data = response.json()
        assert "providers" in data
        assert "configured" in data["providers"]
        assert "total_models" in data["providers"]
        # Should list at least one configured provider (ollama is always available)
        configured = data["providers"]["configured"]
        assert len(configured) >= 1, "Should have at least one configured provider"
        assert "ollama" in configured, "ollama should be in configured providers"

    def test_v1_models_requires_auth(self, client):
        """/v1/models should reject requests without auth."""
        response = client.get("/v1/models")
        assert response.status_code == 401

    def test_v1_models_works_with_correct_key(self, client):
        """/v1/models should work with the master key."""
        response = client.get("/v1/models", headers=_auth_headers())
        assert response.status_code == 200
        data = response.json()
        assert "data" in data
        assert len(data["data"]) >= 1, "Should have at least one model"

    def test_dashboard_events_stream_rejects_invalid_token(self, client):
        """Dashboard SSE should reject an invalid token with 401."""
        response = client.get("/dashboard/events/stream?token=definitely-wrong")
        assert response.status_code == 401

    @patch("proxym.config.get_settings")
    def test_dashboard_events_stream_accepts_master_key(self, mock_get_settings):
        """The SSE token check must accept the configured master key."""
        from types import SimpleNamespace
        mock_get_settings.return_value = SimpleNamespace(
            master_key="sk-test-key",
            default_api_key=None
        )
        assert _check_token("sk-test-key") is True

    def test_dashboard_jsx_loadable(self, client):
        """/proxym-dashboard.jsx should load with Dashboard component."""
        response = client.get("/proxym-dashboard.jsx")
        assert response.status_code == 200
        content = response.text
        # panel.jsx is the entry point; components are in separate files
        assert "function Dashboard" in content
        assert "HomeTab" in content
        assert "VoiceChat" in content
        assert "SystemPanel" in content

    def test_dashboard_hook_js_loadable(self, client):
        """/dashboard-hook.js should contain useProviders."""
        response = client.get("/dashboard-hook.js")
        assert response.status_code == 200
        content = response.text
        assert "useProviders" in content

    def test_dashboard_api_js_uses_lazy_config(self, client):
        """/dashboard-api.js should use lazy getters (not constants)."""
        response = client.get("/dashboard-api.js")
        assert response.status_code == 200
        content = response.text
        # Should use functions, not const assignments for config
        assert "function _api()" in content or "const _api =" in content
        assert "APP_CONFIG" in content

    def test_dashboard_layout_no_duplicate_tabs(self, client):
        """/dashboard-layout.jsx should have unique tab IDs and unified tasks view.
        
        This prevents recurrence of duplicate tabs (human, tickets) that were
        consolidated into the unified Tasks tab with agent filtering.
        """
        response = client.get("/dashboard-layout.jsx")
        assert response.status_code == 200
        content = response.text
        
        # Extract MAIN_TABS array using regex
        import re
        tabs_match = re.search(r'const MAIN_TABS = \[(.*?)\];', content, re.DOTALL)
        assert tabs_match, "MAIN_TABS not found in layout.jsx"
        tabs_content = tabs_match.group(1)
        
        # Extract all tab IDs
        tab_ids = re.findall(r'id:\s*"([^"]+)"', tabs_content)
        
        # Check no duplicates
        assert len(tab_ids) == len(set(tab_ids)), f"Duplicate tab IDs found: {tab_ids}"
        
        # Check unified tasks view: human and tickets should NOT be separate tabs
        # (they are accessed via filter in TasksPanel or old URLs redirect via alias)
        assert "human" not in tab_ids, "human tab should not exist (use Tasks with filter)"
        assert "tickets" not in tab_ids, "tickets tab should not exist (unified with Tasks)"
        
        # system tab is allowed (separate panel, not a duplicate)
        assert "system" in tab_ids, "system tab should exist"
        
        # tasks tab must exist
        assert "tasks" in tab_ids, "tasks tab must exist (unified view)"

    def test_dashboard_layout_tabs_match_valid_set(self, client):
        """MAIN_TABS IDs should match DASHBOARD_VALID_TABS in useDashboardData."""
        import re
        
        # Get layout tabs
        layout_resp = client.get("/dashboard-layout.jsx")
        assert layout_resp.status_code == 200
        layout_content = layout_resp.text
        tabs_match = re.search(r'const MAIN_TABS = \[(.*?)\];', layout_content, re.DOTALL)
        assert tabs_match
        tab_ids = set(re.findall(r'id:\s*"([^"]+)"', tabs_match.group(1)))
        
        # Get valid tabs from useDashboardData
        hook_resp = client.get("/dashboard-hook.js")
        assert hook_resp.status_code == 200
        hook_content = hook_resp.text
        
        # Extract DASHBOARD_VALID_TABS
        valid_match = re.search(r'DASHBOARD_VALID_TABS = new Set\(\[(.*?)\]\)', hook_content, re.DOTALL)
        assert valid_match, "DASHBOARD_VALID_TABS not found"
        valid_ids = set(re.findall(r'"([^"]+)"', valid_match.group(1)))
        
        # Layout tabs should be subset of valid tabs
        assert tab_ids.issubset(valid_ids), f"Layout has invalid tabs: {tab_ids - valid_ids}"
        
        # Check aliases exist for removed tabs (human, tickets -> tasks)
        assert 'human: "tasks"' in hook_content or 'tickets: "tasks"' in hook_content, \
            "Aliases for human/tickets should exist to redirect old URLs"
