# -*- coding: utf-8 -*-
"""
QwenPaw Voice Transcription module P0 end-to-end tests

Combined test design:
- VOICE-001: Voice page load + config display + help info
- VOICE-002: Voice service enable/disable
- VOICE-003: Voice service config (Twilio, etc.) + input validation
- VOICE-004: Voice channel status monitoring
- VOICE-005: API operation validation

Run command: pytest tests/test_voice_p0.py -v
"""
from __future__ import annotations

import logging
import pytest
from playwright.sync_api import Page, expect, TimeoutError

from config.settings import config
from utils.helpers import log_test_step, log_test_result

logger = logging.getLogger(__name__)

VOICE_URL = f"{config.base_url}/settings/voice"


def navigate_to_voice(page: Page):
    """Navigate to the voice transcription page and wait for load."""
    page.goto(VOICE_URL)
    page.wait_for_load_state("domcontentloaded")
    page.wait_for_timeout(3000)


# ============================================================================
# VOICE-001: Page load + config display
# ============================================================================

@pytest.mark.integration
@pytest.mark.p0
@pytest.mark.voice_core
class TestVoiceConfigDisplay:
    """
    VOICE-001: Voice transcription page load + config display + help info

    Functional coverage:
    1. Voice transcription page access and load
    2. Breadcrumb navigation validation
    3. Voice service toggle display
    4. Config form display (Twilio, etc.)
    5. Service status display
    6. Help info and hints display
    """

    @pytest.mark.test_id("VOICE-001")
    def test_voice_config_display(self, page: Page, request: pytest.FixtureRequest):
        """Verify voice transcription config displays correctly, including help info and hints."""
        test_name = request.node.name

        # Step 1: Navigate to voice transcription page
        log_test_step("1. Navigate to voice transcription page")
        navigate_to_voice(page)

        # Step 2: Verify page loaded (voice page has no breadcrumb).
        # The page renders asynchronously and can be slow on a loaded CI
        # runner, so wait for actual voice content/controls rather than just
        # `body` visibility (which fires before the SPA route content mounts).
        log_test_step("2. Verify page loaded")
        page_loaded = page.locator('body').first
        expect(page_loaded).to_be_visible(timeout=5000)
        try:
            page.wait_for_selector(
                '.qwenpaw-radio-group, .qwenpaw-radio-wrapper, '
                '.qwenpaw-switch, '
                'h1:has-text("Voice"), .qwenpaw-page-header:has-text("Voice")',
                timeout=15000,
            )
        except Exception:
            logger.warning(
                "Voice content selectors not visible within 15s; page may "
                "be rendering slowly on this runner"
            )
        logger.info("Voice transcription page loaded")

        # Step 3: Verify page title
        log_test_step("3. Verify page title")
        page_title = page.locator('h1:has-text("Voice"), .qwenpaw-page-header:has-text("Voice")').first
        if page_title.is_visible(timeout=3000):
            logger.info("Page title visible")

        # Step 4: Verify and interact with voice service config controls
        log_test_step("4. Verify voice service config controls")
        # Source: Voice page uses Radio.Group to choose mode (disabled/whisper_api/local_whisper)
        radio_group = page.locator('.qwenpaw-radio-group, .qwenpaw-radio-wrapper').first
        voice_switch = page.locator('.qwenpaw-switch').first

        page_content = page.locator('body').inner_text()
        has_voice_content = any(keyword in page_content for keyword in ['Voice', '语音', 'Transcription', 'STT', 'TTS', 'Whisper', 'Audio'])
        assert has_voice_content, "Voice config page should contain voice-related content"
        logger.info("Page contains voice-related content")

        # Verify there are interactable config controls
        has_radio = radio_group.count() > 0 and radio_group.is_visible(timeout=3000)
        has_switch = voice_switch.count() > 0 and voice_switch.is_visible(timeout=2000)
        all_controls = page.locator('.qwenpaw-radio-wrapper, .qwenpaw-switch, .qwenpaw-select, input').all()
        assert len(all_controls) > 0, "Voice page should have at least one interactable config control"
        logger.info(f"Found {len(all_controls)} config controls (Radio={'yes' if has_radio else 'no'}, Switch={'yes' if has_switch else 'no'})")

        # Step 5: Verify config form
        log_test_step("5. Verify config form fields")
        form_fields = page.locator('.qwenpaw-form-item, .qwenpaw-radio-wrapper, input, .qwenpaw-select, textarea').all()
        assert len(form_fields) > 0, "Voice page should have at least one form field"
        logger.info(f"Found {len(form_fields)} form fields")

        # Step 6: Verify controls are clickable/interactable
        log_test_step("6. Verify controls are interactable")
        if has_radio:
            radio_items = page.locator('.qwenpaw-radio-wrapper').all()
            assert len(radio_items) >= 2, f"Should have at least 2 Radio options, actual {len(radio_items)}"
            logger.info(f"Radio.Group has {len(radio_items)} options")
        elif has_switch:
            aria_checked = voice_switch.get_attribute('aria-checked')
            assert aria_checked is not None, "Switch should have aria-checked attribute"
            logger.info(f"Switch current state: {aria_checked}")

        # Step 7: Verify save button exists
        log_test_step("7. Verify save button")
        save_btn = page.locator('button:has-text("保存"), button:has-text("Save"), button.qwenpaw-btn-primary').first
        if save_btn.count() > 0 and save_btn.is_visible(timeout=3000):
            assert save_btn.is_enabled(), "Save button should be enabled"
            logger.info("Save button exists and is enabled")
        else:
            logger.info("No standalone save button found (may auto-save)")

        log_test_result(test_name, "PASS", "Voice transcription config display and controls validation passed")


# ============================================================================
# VOICE-002: Voice service enable/disable
# ============================================================================

@pytest.mark.integration
@pytest.mark.p0
@pytest.mark.voice_toggle
class TestVoiceToggle:
    """
    VOICE-002: Voice service enable/disable

    Functional coverage:
    1. Enable voice service
    2. Disable voice service
    3. State toggle validation
    4. Save config
    """

    @pytest.mark.test_id("VOICE-002")
    def test_voice_service_toggle(self, page: Page, request: pytest.FixtureRequest):
        """Verify voice service toggle."""
        test_name = request.node.name

        # Step 1: Navigate to voice transcription page
        log_test_step("1. Navigate to voice transcription page")
        navigate_to_voice(page)

        # Step 2: Find voice service config control
        # Source: Voice page uses Radio.Group to choose provider type (disabled/whisper_api/local_whisper)
        # rather than a Switch
        log_test_step("2. Find voice service config control")

        # Prefer Radio.Group (actual UI structure)
        radio_group = page.locator('.qwenpaw-radio-group, .qwenpaw-radio-wrapper').first
        has_radio = radio_group.count() > 0 and radio_group.is_visible(timeout=5000)

        # Also check for Switch (in case of UI variant)
        voice_toggle = page.locator('.qwenpaw-switch').first
        has_switch = voice_toggle.count() > 0 and voice_toggle.is_visible(timeout=3000)

        # Fallback: check whether any interactable config controls exist (select/input also count)
        all_controls = page.locator(
            '.qwenpaw-radio-group, .qwenpaw-radio-wrapper, .qwenpaw-switch, '
            '.qwenpaw-select, .ant-select, input, select, textarea, '
            '[class*="card"], .qwenpaw-card'
        ).all()
        visible_controls = [c for c in all_controls if c.is_visible()]

        if has_radio:
            logger.info("Found Radio.Group config control (provider type selection)")

            # Get all radio options
            radio_items = page.locator('.qwenpaw-radio-wrapper').all()
            assert len(radio_items) >= 2, f"Should have at least 2 config options, actual {len(radio_items)}"
            logger.info(f"Found {len(radio_items)} config options")

            # Get the currently checked option
            checked_radio = page.locator('.qwenpaw-radio-wrapper-checked, .qwenpaw-radio-wrapper.qwenpaw-radio-wrapper-checked').first
            initial_text = ""
            if checked_radio.count() > 0:
                initial_text = checked_radio.text_content() or ""
                logger.info(f"Currently checked: {initial_text[:50]}")

            # Step 3: Switch to another option
            log_test_step("3. Switch to another config option")
            switched = False
            for radio_item in radio_items:
                item_class = radio_item.get_attribute('class') or ""
                if 'checked' not in item_class:
                    radio_item.click()
                    page.wait_for_timeout(1000)
                    switched = True
                    new_text = radio_item.text_content() or ""
                    logger.info(f"Switched to: {new_text[:50]}")
                    break

            assert switched, "Should successfully switch to another option"

            # Verify checked state changed
            new_checked = page.locator('.qwenpaw-radio-wrapper-checked, .qwenpaw-radio-wrapper.qwenpaw-radio-wrapper-checked').first
            if new_checked.count() > 0:
                new_checked_text = new_checked.text_content() or ""
                assert new_checked_text != initial_text or initial_text == "", "Checked option should have changed"
                logger.info("Config option switched successfully")

            # Step 4: Verify save button enabled and click save
            log_test_step("4. Save config")
            save_btn = page.locator('button:has-text("保存"), button:has-text("Save"), button.qwenpaw-btn-primary').first
            if save_btn.count() > 0 and save_btn.is_visible(timeout=3000):
                save_btn.click()
                page.wait_for_timeout(2000)
                logger.info("Save button clicked")
            else:
                logger.info("No save button found (may auto-save)")

            # Step 5: Restore original state
            log_test_step("5. Restore original state")
            # Find the originally checked option and click back
            for radio_item in radio_items:
                item_text = radio_item.text_content() or ""
                if initial_text and initial_text[:20] in item_text:
                    radio_item.click()
                    page.wait_for_timeout(1000)
                    # Save the restoration
                    if save_btn.count() > 0 and save_btn.is_visible(timeout=3000):
                        save_btn.click()
                        page.wait_for_timeout(1000)
                    logger.info("Original config restored")
                    break

        elif has_switch:
            logger.info("Found Switch control")

            # Get current state
            toggle_class = voice_toggle.get_attribute('class')
            initial_state = 'checked' in toggle_class if toggle_class else False
            logger.info(f"Initial state: {'enabled' if initial_state else 'disabled'}")

            # Step 3: Toggle switch
            log_test_step("3. Toggle switch")
            voice_toggle.click()
            page.wait_for_timeout(1000)

            # Verify state after toggle
            new_toggle_class = voice_toggle.get_attribute('class')
            new_state = 'checked' in new_toggle_class if new_toggle_class else False
            assert initial_state != new_state, "Switch state should change"
            logger.info("Switch state toggled successfully")

            # Step 4: Verify save
            log_test_step("4. Verify save")
            page.wait_for_timeout(1000)

            # Step 5: Restore original state
            log_test_step("5. Restore original state")
            voice_toggle.click()
            page.wait_for_timeout(1000)
            logger.info("State restored")
        else:
            # Fallback: check whether any interactable config controls exist (including select/input)
            assert len(visible_controls) > 0, "No voice service config control found (Radio/Switch/Card/Select/Input)"
            logger.info(f"Found {len(visible_controls)} config controls; voice page uses non-standard layout")

            # Verify select control interactivity
            voice_select = page.locator('.qwenpaw-select, .ant-select').first
            if voice_select.count() > 0 and voice_select.is_visible():
                voice_select.click()
                page.wait_for_timeout(1000)
                # Check dropdown options
                dropdown_items = page.locator('.qwenpaw-select-item, .ant-select-item, [class*=select-item]').all()
                logger.info(f"Select control is interactable, found {len(dropdown_items)} dropdown options")
                page.keyboard.press("Escape")
                page.wait_for_timeout(500)

        log_test_result(test_name, "PASS", "Voice service config toggle validation passed")


# ============================================================================
# VOICE-003: Voice service config
# ============================================================================

@pytest.mark.integration
@pytest.mark.p0
@pytest.mark.voice_config
class TestVoiceServiceConfig:
    """
    VOICE-003: Voice service config (Twilio, etc.) + input validation

    Functional coverage:
    1. Twilio config form
    2. Account SID config
    3. Auth Token config
    4. Phone Number config
    5. Webhook URL display
    6. Config save
    7. Input validation and required-field markers
    """

    @pytest.mark.test_id("VOICE-003")
    def test_twilio_config_form(self, page: Page, request: pytest.FixtureRequest):
        """Verify Twilio config form, including input validation and required-field markers."""
        test_name = request.node.name

        # Step 1: Navigate to voice transcription page
        log_test_step("1. Navigate to voice transcription page")
        navigate_to_voice(page)

        # Step 2: Verify Twilio or voice config area
        log_test_step("2. Verify voice service config area")
        twilio_section = page.locator('[class*=twilio], .qwenpaw-card:has-text("Twilio"), .qwenpaw-collapse:has-text("Twilio")').first
        page_content = page.locator('body').inner_text()
        has_twilio_content = any(keyword in page_content for keyword in ['Twilio', 'Account SID', 'Auth Token', 'Phone', 'Webhook'])
        # Also accept generic voice config keywords (Twilio may not be enabled in some environments)
        has_voice_content = any(keyword in page_content for keyword in ['Voice', '语音', 'Transcription', 'STT', 'TTS', 'Whisper', 'Audio', '转写'])
        assert has_twilio_content or twilio_section.count() > 0 or has_voice_content, \
            "Voice config page should contain Twilio or voice-related content"
        if has_twilio_content:
            logger.info("Page contains Twilio config content")
        else:
            logger.info("Page lacks Twilio content but has generic voice config content")

        # Step 3: Verify config fields and test input
        log_test_step("3. Verify config fields and test input")
        all_inputs = page.locator('input[type="text"], input[type="password"], .qwenpaw-input input, input').all()
        # Filter out readonly and combobox inputs (e.g. select search input)
        visible_inputs = [
            inp for inp in all_inputs
            if inp.is_visible()
            and not inp.get_attribute("readonly")
            and inp.get_attribute("role") != "combobox"
        ]

        if len(visible_inputs) > 0:
            logger.info(f"Found {len(visible_inputs)} editable inputs")

            # Type a test value into the first input to verify interactivity
            first_input = visible_inputs[0]
            original_value = first_input.input_value()
            test_value = "e2e_test_placeholder_value"
            first_input.fill(test_value)
            page.wait_for_timeout(500)
            filled_value = first_input.input_value()
            assert filled_value == test_value, \
                f"Input should accept text, expected '{test_value}', actual '{filled_value}'"
            logger.info("Input accepts typing")

            # Restore original value
            first_input.fill(original_value)
            page.wait_for_timeout(300)
        else:
            # This environment has no editable inputs; verify select controls as config entry
            selects = page.locator('.qwenpaw-select, .ant-select').all()
            visible_selects = [s for s in selects if s.is_visible()]
            assert len(visible_selects) > 0, "Voice config page should have at least one visible config control (input or select)"
            logger.info(f"No editable inputs, but found {len(visible_selects)} Select config controls")

        # Step 4: Verify save button exists and is enabled
        log_test_step("4. Verify save button")
        save_btn = page.locator('button:has-text("Save"), button:has-text("保存"), button.qwenpaw-btn-primary').first
        if save_btn.count() > 0 and save_btn.is_visible(timeout=3000):
            assert save_btn.is_enabled(), "Save button should be enabled"
            logger.info("Save button exists and is enabled")
        else:
            logger.info("No standalone save button found (may auto-save)")

        # Step 5: Verify Webhook URL display
        log_test_step("5. Verify Webhook URL display")
        webhook_url = page.locator('[class*=webhook], .qwenpaw-paragraph:has-text("/voice/")').or_(page.get_by_text("Webhook", exact=False)).first
        if webhook_url.count() > 0 and webhook_url.is_visible(timeout=3000):
            webhook_text = webhook_url.inner_text()
            assert len(webhook_text) > 0, "Webhook URL should not be empty"
            logger.info(f"Webhook URL: {webhook_text[:100]}")
        else:
            logger.info("No Webhook URL display found")


# ============================================================================
# VOICE-P2-001: Audio mode switch + Whisper status detection
# ============================================================================

@pytest.mark.integration
@pytest.mark.p2
@pytest.mark.voice
class TestVoiceModeSwitch:
    """VOICE-P2-001: Audio mode switch + Whisper status detection"""

    @pytest.mark.test_id("VOICE-P2-001")
    def test_voice_mode_switch(self, page: Page, request: pytest.FixtureRequest):
        """Test audio mode switching and Whisper status detection."""
        test_name = request.node.name

        log_test_step("Navigate to voice config page")
        page.goto(f"{config.base_url}/voice")
        page.wait_for_load_state("domcontentloaded")
        page.wait_for_timeout(3000)

        log_test_step("Find audio mode selector")
        mode_select = page.locator(
            '.qwenpaw-select, .ant-select, '
            '.qwenpaw-radio-group, .qwenpaw-segmented'
        ).first
        if mode_select.count() > 0:
            logger.info("Audio mode selector exists")
            if mode_select.locator('.qwenpaw-select-selector').count() > 0:
                mode_select.click()
                page.wait_for_timeout(500)
                options = page.locator('.qwenpaw-select-item-option').all()
                logger.info(f"Found {len(options)} mode options")
                page.keyboard.press("Escape")
        else:
            logger.info("No audio mode selector found")

        log_test_step("Find Whisper status")
        whisper_status = page.locator(
            ':text("Whisper"), :text("whisper"), '
            '[class*="whisper"], [class*="Whisper"]'
        ).first
        if whisper_status.count() > 0:
            logger.info("Found Whisper-related element")
        else:
            logger.info("No Whisper status element found")

        log_test_step("Find switch controls")
        switches = page.locator('.qwenpaw-switch').all()
        # Voice page should have at least some config controls
        all_controls = page.locator('.qwenpaw-switch, .qwenpaw-select, .ant-select, input, .qwenpaw-radio-group').all()
        assert len(all_controls) > 0, "Voice config page should have config controls"
        logger.info(f"Found {len(switches)} switch controls, total {len(all_controls)} config controls")

        log_test_result(test_name, True, 0)
