"""GUI tests for Voice and Diagnostics tabs — endpoints, component serving, structure."""

import pytest
from fastapi.testclient import TestClient
from unittest.mock import patch

# Patch settings before importing app
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']
    # Fallback to settings
    from proxym.config import get_settings
    return get_settings().master_key


class TestVoiceComponent:
    """Test Voice tab component serving and structure."""

    @pytest.fixture
    def client(self):
        return TestClient(app)

    def test_voice_jsx_endpoint_serves(self, client):
        """Voice JSX endpoint should return valid JavaScript."""
        response = client.get("/dashboard-voice.jsx")
        assert response.status_code == 200
        assert "javascript" in response.headers["content-type"]

    def test_voice_jsx_has_component(self, client):
        """Voice JSX should define VoiceChat component."""
        response = client.get("/dashboard-voice.jsx")
        assert "function VoiceChat" in response.text

    def test_voice_jsx_has_speech_api_detection(self, client):
        """Voice JSX should detect SpeechRecognition and SpeechSynthesis support."""
        content = client.get("/dashboard-voice.jsx").text
        assert "SpeechRecognition" in content
        assert "speechSynthesis" in content

    def test_voice_jsx_has_stt_controls(self, client):
        """Voice JSX should have start/stop listening logic."""
        content = client.get("/dashboard-voice.jsx").text
        assert "startListening" in content
        assert "stopAll" in content

    def test_voice_jsx_has_tts_controls(self, client):
        """Voice JSX should have TTS speak/stop logic."""
        content = client.get("/dashboard-voice.jsx").text
        assert "_speakTTS" in content
        assert "stopAll" in content

    def test_voice_jsx_has_llm_integration(self, client):
        """Voice JSX + engine should call /v1/chat/completions for LLM responses."""
        voice = client.get("/dashboard-voice.jsx").text
        engine = client.get("/dashboard-voice-engine.jsx").text
        combined = voice + engine
        assert "/v1/chat/completions" in combined
        assert "processVoiceInput" in combined

    def test_voice_jsx_has_model_selector(self, client):
        """Voice JSX should offer model selection (cheap, balanced, local, etc)."""
        content = client.get("/dashboard-voice.jsx").text
        assert "cheap" in content
        assert "balanced" in content
        assert "local" in content

    def test_voice_jsx_has_language_selector(self, client):
        """Voice JSX should support language switching (pl-PL, en-US)."""
        content = client.get("/dashboard-voice.jsx").text
        assert "pl-PL" in content
        assert "en-US" in content

    def test_voice_jsx_has_keyboard_shortcut(self, client):
        """Voice JSX should support Space key to toggle mic."""
        content = client.get("/dashboard-voice.jsx").text
        assert "Space" in content

    def test_voice_jsx_no_external_dependencies(self, client):
        """Voice JSX should use only Web Speech API (no external STT/TTS imports)."""
        content = client.get("/dashboard-voice.jsx").text
        # Should NOT import heavy external libs
        assert "import whisper" not in content.lower()
        assert "import openai" not in content.lower()


class TestDiagnosticsComponent:
    """Test Diagnostics tab component serving and structure."""

    @pytest.fixture
    def client(self):
        return TestClient(app)

    def test_diagnostics_jsx_endpoint_serves(self, client):
        """Diagnostics JSX endpoint should return valid JavaScript."""
        response = client.get("/dashboard-diagnostics.jsx")
        assert response.status_code == 200
        assert "javascript" in response.headers["content-type"]

    def test_diagnostics_jsx_has_panel(self, client):
        """Diagnostics JSX should define DiagnosticsPanel component."""
        content = client.get("/dashboard-diagnostics.jsx").text
        assert "function DiagnosticsPanel" in content

    def test_diagnostics_jsx_has_sub_components(self, client):
        """Diagnostics JSX should include all sub-components (concatenated)."""
        content = client.get("/dashboard-diagnostics.jsx").text
        # helpers
        assert "_diagBadge" in content or "diagBadge" in content.lower()
        # DiagInfrastructure sub-components
        assert "DiagVSCode" in content
        assert "DiagProxy" in content
        # DiagConfig sub-components
        assert "DiagRooConfig" in content
        assert "DiagProviders" in content
        # DiagExtensions sub-components
        assert "DiagExtensions" in content
        assert "DiagCopilot" in content
        # DiagAgent sub-components
        assert "DiagAgentSetup" in content
        assert "DiagAgentTest" in content

    def test_diagnostics_jsx_calls_backend(self, client):
        """Diagnostics JSX should call /tests/status endpoint."""
        content = client.get("/dashboard-diagnostics.jsx").text
        assert "/tests/status" in content

    def test_diagnostics_jsx_no_duplicate_functions(self, client):
        """Diagnostics JSX should not have duplicate function definitions."""
        content = client.get("/dashboard-diagnostics.jsx").text
        # Each function should be defined exactly once
        for func in ["DiagnosticsPanel", "DiagVSCode", "DiagProxy"]:
            count = content.count(f"function {func}")
            assert count == 1, f"function {func} defined {count} times (expected 1)"


class TestDiagnosticsBackend:
    """Test diagnostics backend /tests/* endpoints."""

    @pytest.fixture
    def client(self):
        c = TestClient(app)
        c.headers["Authorization"] = f"Bearer {_get_master_key()}"
        return c

    @pytest.mark.slow
    def test_tests_status_endpoint(self, client):
        """GET /tests/status should return diagnostic results."""
        response = client.get("/tests/status")
        assert response.status_code == 200
        data = response.json()
        assert "overall_status" in data
        assert "passed" in data
        assert "failed" in data
        assert "total" in data
        assert "tests" in data
        assert isinstance(data["tests"], list)

        # Normalized per-test fields required by the dashboard UI.
        for t in data["tests"]:
            assert "service" in t
            assert "passed" in t
            assert isinstance(t["passed"], bool)
            assert "message" in t
            assert isinstance(t["message"], str)
            assert "issues" in t
            assert isinstance(t["issues"], list)

    @pytest.mark.slow
    def test_tests_status_has_all_services(self, client):
        """GET /tests/status should check all 8 services."""
        response = client.get("/tests/status", timeout=10)
        data = response.json()
        services = {t.get("service") for t in data["tests"]}
        expected = {"vscode", "llm_proxy", "roo_cline", "extensions",
                    "providers", "copilot_conflict", "agent_setup", "agent_test"}
        assert expected == services, f"Missing services: {expected - services}"

    def test_tests_proxy_endpoint(self, client):
        """GET /tests/proxy should return proxy health info."""
        response = client.get("/tests/proxy")
        assert response.status_code == 200
        data = response.json()
        assert data["service"] == "llm_proxy"
        assert "reachable" in data

    def test_tests_providers_endpoint(self, client):
        """GET /tests/providers should list configured providers."""
        response = client.get("/tests/providers")
        assert response.status_code == 200
        data = response.json()
        assert data["service"] == "providers"
        assert "providers" in data
        assert "aliases" in data
        assert data["total_providers"] >= 1


class TestConsoleEndpoints:
    """Test console/routing API endpoints."""

    @pytest.fixture
    def client(self):
        c = TestClient(app)
        c.headers["Authorization"] = f"Bearer {_get_master_key()}"
        return c

    def test_routing_test_endpoint(self, client):
        """GET /api/v1/routing/test should return routing status per alias."""
        response = client.get("/api/v1/routing/test")
        assert response.status_code == 200
        data = response.json()
        assert "routing" in data
        assert "summary" in data
        assert "total" in data["summary"]
        assert "ok" in data["summary"]

    def test_routing_ollama_endpoint(self, client):
        """GET /api/v1/routing/ollama should return Ollama status."""
        response = client.get("/api/v1/routing/ollama")
        assert response.status_code == 200
        data = response.json()
        assert "ollama_url" in data
        assert "reachable" in data
        assert "models" in data

    def test_vm_console_not_found(self, client):
        """GET /api/v1/vms/nonexistent/console should return 404."""
        response = client.get("/api/v1/vms/nonexistent/console")
        assert response.status_code == 404


class TestDashboardHTMLIntegrity:
    """Test that dashboard.html loads all required components."""

    @pytest.fixture
    def client(self):
        return TestClient(app)

    def test_html_loads_voice_jsx(self, client):
        """Dashboard HTML should include voice.jsx script tag."""
        content = client.get("/dashboard-ui").text
        assert "/dashboard-voice.jsx" in content

    def test_html_loads_diagnostics_jsx(self, client):
        """Dashboard HTML should include diagnostics.jsx script tag."""
        content = client.get("/dashboard-ui").text
        assert "/dashboard-diagnostics.jsx" in content

    def test_html_no_duplicate_diagnostics_scripts(self, client):
        """Dashboard HTML should NOT load individual diag sub-components separately."""
        content = client.get("/dashboard-ui").text
        # These should NOT appear as separate script tags — they're concatenated
        assert "/dashboard-diag-helpers.jsx" not in content
        assert "/dashboard-diag-infrastructure.jsx" not in content
        assert "/dashboard-diag-config.jsx" not in content
        assert "/dashboard-diag-extensions.jsx" not in content
        assert "/dashboard-diag-agent.jsx" not in content

    def test_html_script_load_order(self, client):
        """Scripts should load in correct dependency order."""
        content = client.get("/dashboard-ui").text
        # api.js before hook.js before components before panel.jsx
        api_pos = content.index("/dashboard-api.js")
        hook_pos = content.index("/dashboard-hook.js")
        voice_pos = content.index("/dashboard-voice.jsx")
        diag_pos = content.index("/dashboard-diagnostics.jsx")
        panel_pos = content.index("/proxym-dashboard.jsx")
        assert api_pos < hook_pos < voice_pos < diag_pos < panel_pos

    def test_panel_jsx_references_all_tabs(self, client):
        """panel.jsx should reference all tab components."""
        content = client.get("/proxym-dashboard.jsx").text
        assert "VoiceChat" in content
        assert "SystemPanel" in content
        assert "HomeTab" in content
        assert "HumanTaskPanel" in content
        assert "AccountsPanel" in content
        assert "VMsPanel" in content
        assert "UsersPanel" in content

    def test_setup_jsx_references_human_tasks(self, client):
        """setup.jsx should include the human tasks panel reference."""
        content = client.get("/dashboard-setup.jsx").text
        assert "HumanTaskPanel" in content

    def test_system_jsx_has_human_subsection(self, client):
        """system.jsx should stay focused on system sections only."""
        content = client.get("/dashboard-system.jsx").text
        assert 'section === "human"' in content
        assert "HumanTaskPanel" in content

    def test_layout_jsx_has_all_tabs(self, client):
        """layout.jsx should define all tab IDs."""
        content = client.get("/dashboard-layout.jsx").text
        for tab_id in ["home", "projects", "tasks", "human", "voice", "accounts", "models", "vms", "tickets", "tools", "users", "setup", "system"]:
            assert tab_id in content, f"Tab '{tab_id}' missing from layout.jsx"

    def test_hook_js_valid_tabs_complete(self, client):
        """useDashboardData hook should include all tabs in validTabs."""
        content = client.get("/dashboard-hook.js").text
        for tab_id in ["home", "projects", "tasks", "human", "voice", "accounts", "models", "vms", "tickets", "tools", "users", "setup", "system"]:
            assert tab_id in content, f"Tab '{tab_id}' missing from validTabs in hook"


class TestAllComponentEndpoints:
    """Verify every JSX/JS component endpoint returns 200."""

    @pytest.fixture
    def client(self):
        return TestClient(app)

    @pytest.mark.parametrize("path", [
        "/dashboard-api.js",
        "/dashboard-formatters.js",
        "/dashboard-hook.js",
        "/dashboard-ui-components.jsx",
        "/dashboard-models.jsx",
        "/dashboard-accounts.jsx",
        "/dashboard-users.jsx",
        "/dashboard-layout.jsx",
        "/dashboard-overview.jsx",
        "/dashboard-vms.jsx",
        "/dashboard-logs.jsx",
        "/dashboard-voice.jsx",
        "/dashboard-diagnostics.jsx",
        "/proxym-dashboard.jsx",
    ])
    def test_component_endpoint_returns_200(self, client, path):
        """Each component endpoint should serve content successfully."""
        response = client.get(path)
        assert response.status_code == 200, f"{path} returned {response.status_code}"
        assert len(response.text) > 10, f"{path} returned too little content"
        assert "javascript" in response.headers["content-type"]
