import asyncio
from typing import Dict, Any
from playwright.async_api import Page
from backend.core.logging_config import log_info
from backend.utils.frames_handler import collect_all_frames
from backend.utils.reggex import (
    normalize_step, parse_direct_url, is_remove_button, parse_type_allowed,
    is_open_family, disambiguate_add_website, parse_toggle, parse_verify_button,
    parse_verify_text, is_back
)
from backend.config.regex import extract_account_name, extract_family_member, extract_double_click_target
from backend.config.xpath import XPATH_REMOVE_BUTTON_DYNAMIC


class NaturalStepMixin:
    """Mixin class providing natural language step execution capability."""
    
    async def run_natural_test_step(self, page: Page, step: str) -> Dict[str, Any]:
        """Main method to execute a natural language test step."""
        print(f"\n[INFO] Executing step: {step}")
        
        # Normalize step using helper
        step_lower, step_normalized = normalize_step(step)
        self.logger.debug(f"[DEBUG] step_lower: '{step_lower}' | step_normalized: '{step_normalized}'")
        # Generic regex: login with "<account name>" account
        account_name = extract_account_name(step_lower)
        if account_name:
            self.logger.info(f"ADO step detected: Login with '{account_name}' account (dynamic)")
            switch_result = await self.account_manager.switch_account_if_needed(page, account_name=account_name)
            return {
                 "action": {
                    "type": "switch_and_signin_account",
                    "confidence": 0.99,
                    "reasoning": f"Triggered by ADO step: Login with '{account_name}' account"
                 },
                    "success": True if switch_result else False,
                    "details": {
                        "switch_result": switch_result,
                        "step": step,
                        "description": f"Login with {account_name} account and sign in if not already logged in",
                        "reasoning": f"Triggered by ADO step: Login with '{account_name}' account",
                        "element_info": {},
                        "frame": "main"
                }
            }
        
        # Regex: Switch "<any account>" Family member (dynamic)
        account_name = extract_family_member(step_lower)
        if account_name:
            self.logger.info(f"ADO step detected: Switch '{account_name}' Family member (dynamic)")
            switch_result = await self.account_manager.switch_family_member(page, account_name=account_name)
            return {
                "action": {
                    "type": "switch_family_member",
                    "confidence": 0.99,
                    "reasoning": f"Triggered by ADO step: Switch '{account_name}' Family member"
                },
                "success": True if switch_result else False,
                "details": {
                    "switch_result": switch_result,
                    "step": step,
                    "description": f"Switch to {account_name} family member",
                    "reasoning": f"Triggered by ADO step: Switch '{account_name}' Family member",
                    "element_info": {},
                    "frame": "main"
                }
            }

        # Explicit verification and forced actions using helper functions
        try:
            url = parse_direct_url(step)
            if url:
                forced_action = {
                    "action": "navigate",
                    "locator": "url",
                    "value": url,
                    "description": f"Navigate to {url}",
                    "confidence": 0.99,
                    "reasoning": "Explicit @URL provided in step",
                    "step": step,
                }
                return await self.execute_action(page, forced_action)

            if is_remove_button(step):
                forced_action = {
                    "action": "click",
                    "locator": "xpath",
                    "value": XPATH_REMOVE_BUTTON_DYNAMIC,
                    "description": "Click dynamic 'Remove' button for a site entry",
                    "confidence": 0.99,
                    "reasoning": "Aria-label matches 'Remove <site> from this list' regardless of site",
                    "step": step,
                }
                return await self.execute_action(page, forced_action)

            input_text = parse_type_allowed(step)
            if input_text:
                forced_action = {
                    "action": "type",
                    "locator": "id",
                    "value": "web-search-always-allowed-input",
                    "input_text": input_text,
                    "description": f"Type '{input_text}' into Allowed sites textbox",
                    "confidence": 0.99,
                    "reasoning": "Explicit instruction to type into Allowed sites textbox",
                    "step": step,
                }
                return await self.execute_action(page, forced_action)

            if is_open_family(step_lower):
                forced_action = {
                    "action": "navigate",
                    "locator": "url",
                    "value": "https://account.microsoft.com/family/home",
                    "description": "Open Family Safety homepage",
                    "confidence": 0.99,
                    "reasoning": "Recognized explicit instruction to open Family Safety webpage",
                    "step": step,
                }
                return await self.execute_action(page, forced_action)

            add_ctx = disambiguate_add_website(step_lower)
            if add_ctx == "allow":
                forced_action = {
                    "action": "click",
                    "locator": "id",
                    "value": "web-search-always-allowed-input",
                    "description": "Click 'Add a website' input in Allow sites",
                    "confidence": 0.99,
                    "reasoning": "Disambiguated by 'Allow sites' context",
                    "step": step,
                }
                return await self.execute_action(page, forced_action)
            if add_ctx == "block":
                forced_action = {
                    "action": "click",
                    "locator": "id",
                    "value": "web-search-never-allowed-input",
                    "description": "Click 'Add a website' input in Block sites",
                    "confidence": 0.99,
                    "reasoning": "Disambiguated by 'Block sites' context",
                    "step": step,
                }
                return await self.execute_action(page, forced_action)

            toggle = parse_toggle(step)
            if toggle:
                label, desired = toggle
                forced_action = {
                    "action": "ensure_toggle",
                    "locator": "label_text",
                    "value": label,
                    "desired_on": desired,
                    "description": f"Ensure toggle '{label}' is {'ON' if desired else 'OFF'}",
                    "confidence": 0.99,
                    "reasoning": "Explicit ensure toggle instruction",
                    "step": step,
                }
                return await self.execute_action(page, forced_action)
        except Exception:
            pass
 
        try:
            # Handle verification of buttons with aria-label using helper
            button_label = parse_verify_button(step)
            if button_label:
                # Check if button with aria-label is visible without clicking
                frames = collect_all_frames(page)
                button_found = False
                for frame in frames:
                    try:
                        btn = await frame.query_selector(f"button[aria-label='{button_label}']")
                        if btn:
                            bbox = await btn.bounding_box()
                            if bbox and bbox.get('width', 0) > 0 and bbox.get('height', 0) > 0:
                                button_found = True
                                break
                    except Exception:
                        pass
                
                return {
                    "action": {"action": "verify", "locator": "aria-label", "value": button_label, "description": f"Verify '{button_label}' button is visible"},
                    "success": button_found,
                    "error": None if button_found else f"Element not found with aria-label='{button_label}'",
                    "details": {
                        "verification": {"button": button_label, "visible": button_found},
                        "element_info": {"aria-label": button_label}
                    }
                }
            
            # Handle verification text using helpers
            target_text = parse_verify_text(step)
            if target_text:
                forced_action = {
                    "action": "scroll",
                    "locator": "text",
                    "value": target_text,
                    "description": f"Verify text '{target_text}' exists by scrolling into view",
                    "confidence": 0.99,
                    "reasoning": "Explicit verification step: pass if text exists on page",
                    "step": step,
                }
                result = await self.execute_action(page, forced_action)
                # Remap description on success/failure to make it clear it's a verification
                if result.get("success"):
                    result["details"]["verification"] = {"text": target_text, "found": True}
                else:
                    result["error"] = result.get("error") or f"Text not found: {target_text}"
                    result["details"]["verification"] = {"text": target_text, "found": False}
                return result
            # Handle Go Back / Back steps using helper
            if is_back(step):
                forced_action = {
                    "action": "go_back",
                    "locator": "history",
                    "value": "back",
                    "description": "Navigate back to previous page",
                    "confidence": 0.99,
                    "reasoning": "Explicit back navigation instruction",
                    "step": step,
                }
                return await self.execute_action(page, forced_action)
        except Exception:
            pass
 
        # Wait for page to be ready before executing actions
        try:
            await self._wait_for_page_ready(page, timeout_seconds=10)
        except Exception as e:
            print(f"[DEBUG] Page ready check failed, continuing anyway: {e}")

        # Get current page context
        page_context = await self.get_page_context(page)
        
        # Map step to action using LLM
        action = await self.map_natural_step_to_action(step, page_context)
        # Support explicit double click phrasing
        try:
            dbl_click = extract_double_click_target(step_lower)
            if dbl_click:
                target, force_text_span = dbl_click
                action["action"] = "dblclick"
                action["locator"] = "text"
                action["value"] = target
                action["force_text_span"] = force_text_span or action.get("force_text_span", False)
        except Exception:
            pass
        print(f"[INFO] Mapped action: {action['description']} (confidence: {action.get('confidence', 0)})")
        
        # Execute the action
        result = await self.execute_action(page, action)
        
         # After action execution, check if a modal appeared
        # If action was a click and it failed or succeeded, check for modal
        if action.get("action") in ["click", "dblclick"]:
            modal_appeared = await self.wait_for_modal_appearance(page, timeout_seconds=2.0)
            if modal_appeared:
                log_info("[INFO] Modal detected after action - DOM has changed")
                # Wait a bit more for modal to fully render
                await asyncio.sleep(0.5)
        
        # If action failed and modal is present, retry with fresh DOM context
        if not result["success"] and await self.detect_modal_presence(page):
            log_info("[INFO] Action failed but modal is present - recollecting DOM and retrying")
            # Recollect page context with modal
            fresh_page_context = await self.get_page_context(page)
            # Remap step to action with fresh context
            fresh_action = await self.map_natural_step_to_action(step, fresh_page_context)
            # Update action with fresh mapping but preserve original intent
            if fresh_action.get("locator") and fresh_action.get("value"):
                action["locator"] = fresh_action["locator"]
                action["value"] = fresh_action["value"]
                action["description"] = fresh_action.get("description", action["description"])
                action["confidence"] = fresh_action.get("confidence", action.get("confidence", 0))
                log_info(f"[INFO] Retrying with fresh DOM context: {action['description']}")
                # Retry the action with fresh context
                result = await self.execute_action(page, action)
        
        if result["success"]:
            print(f"[SUCCESS] {action['description']}")
            # Update current context
            self.current_context.update({
                "last_action": action["description"],
                "last_element": result["details"].get("element_info", {}),
                "current_frame": result["details"].get("frame", "main")
            })
        else:
            print(f"[FAILED] {action['description']}: {result['error']}")
        
        return result
