"""Locator conversion service between different browser libraries."""

import logging
import re
from typing import Optional, Dict, List

from robotmcp.models.config_models import ExecutionConfig

logger = logging.getLogger(__name__)


class LocatorConverter:
    """Converts locators between different browser automation libraries."""
    
    def __init__(self, config: Optional[ExecutionConfig] = None):
        self.config = config or ExecutionConfig()
    
    def convert_locator_for_library(self, locator: str, target_library: str) -> str:
        """
        Convert locator format between different libraries.
        
        SeleniumLibrary supports:
        - id, name, identifier (default strategies)
        - class, tag, xpath, css, dom, link, partial link, data, jquery (explicit)
        - Implicit xpath detection (starts with //)
        
        Browser Library supports:
        - CSS (default)
        - xpath (auto-detected if starts with // or ..)
        - text (finds by text content)
        - id (CSS shorthand)
        - Cascaded selectors with >>
        - Shadow DOM piercing
        
        Args:
            locator: The locator string to convert
            target_library: Target library ("Browser" or "SeleniumLibrary")
            
        Returns:
            str: Converted locator string
        """
        if not locator:
            return locator
        
        if not self.config.ENABLE_LOCATOR_CONVERSION:
            return locator
            
        # Convert SeleniumLibrary locators to Browser Library format
        if target_library == "Browser":
            return self._convert_to_browser_library(locator)
        # Convert Browser Library locators to SeleniumLibrary format  
        elif target_library == "SeleniumLibrary":
            return self._convert_to_selenium_library(locator)
        
        return locator
    
    def _convert_to_browser_library(self, locator: str) -> str:
        """Convert locator to Browser Library format."""
        # Handle explicit strategy syntax (strategy=value or strategy:value)
        if "=" in locator and self._is_explicit_strategy(locator):
            strategy, value = locator.split("=", 1) if "=" in locator else locator.split(":", 1)
            strategy = strategy.lower().strip()
            
            if strategy == "id":
                # id=element -> id=element (Browser supports both #element and id=element)
                return f"id={value}"
            elif strategy == "css":
                # css=selector -> selector (CSS is default in Browser)
                return value
            elif strategy == "xpath":
                # xpath=//path -> //path (Browser auto-detects xpath)
                return value
            elif strategy == "name":
                # name=attr -> [name="attr"]
                return f'[name="{value}"]'
            elif strategy == "class":
                # class=classname -> .classname
                return f".{value}"
            elif strategy == "tag":
                # tag=div -> div
                return value
            elif strategy == "link" or strategy == "partial link":
                # link=text -> text=text (Browser uses text selectors)
                return f'text={value}'
            elif strategy == "data":
                # data=value -> [data-testid="value"] - common convention
                return f'[data-testid="{value}"]'
            elif strategy == "jquery":
                # jquery selectors -> CSS equivalent (best effort)
                if self.config.CONVERT_JQUERY_SELECTORS:
                    return self._convert_jquery_to_css(value)
                else:
                    logger.warning(f"jQuery locator '{locator}' cannot be converted - jQuery conversion disabled")
                    return locator
            elif strategy == "dom":
                # dom expressions can't be directly converted
                logger.warning(f"DOM locator '{locator}' cannot be converted to Browser Library format")
                return locator
            elif strategy == "identifier":
                # identifier -> try id first, fallback to name
                return f'id={value}, [name="{value}"]'
        
        # Handle implicit xpath (starts with // or ..)
        elif locator.startswith("//") or locator.startswith(".."):
            # XPath is auto-detected in Browser Library
            return locator
        
        # Handle CSS shortcuts that might need conversion
        elif locator.startswith("#") or locator.startswith(".") or locator.startswith("["):
            # Already in CSS format, keep as-is
            return locator
        
        # Handle cascaded selectors
        elif self.config.CONVERT_CASCADED_SELECTORS and ">>" in locator:
            # Already in Browser Library cascaded format
            return locator
        
        return locator
    
    def _convert_to_selenium_library(self, locator: str) -> str:
        """Convert locator to SeleniumLibrary format."""
        # Handle text selectors
        if locator.startswith("text="):
            # text=content -> xpath=//*[contains(text(),'content')]
            text_content = locator[5:]
            return f'xpath=//*[contains(text(),"{text_content}")]'
        
        # Handle CSS shortcuts
        elif locator.startswith("#"):
            # #id -> id=id
            return f"id={locator[1:]}"
        elif locator.startswith("."):
            # .class -> css=.class
            return f"css={locator}"
        elif locator.startswith("["):
            # [attr] -> css=[attr]
            return f"css={locator}"
        
        # Handle cascaded selectors (Browser Library specific)
        elif ">>" in locator:
            # Convert >> to descendant CSS selector
            parts = locator.split(">>")
            css_selector = " ".join(part.strip() for part in parts)
            return f"css={css_selector}"
        
        # Handle implicit xpath
        elif locator.startswith("//") or locator.startswith(".."):
            # xpath=//path (explicit for SeleniumLibrary)
            return f"xpath={locator}"
        
        # Handle id= format (Browser Library)
        elif locator.startswith("id="):
            # Keep id= format as SeleniumLibrary supports it
            return locator
        
        # Plain CSS selector - make it explicit for SeleniumLibrary
        elif self._is_css_selector(locator):
            return f"css={locator}"
        
        return locator
    
    def _convert_jquery_to_css(self, jquery_selector: str) -> str:
        """
        Convert jQuery selectors to CSS equivalents (best effort).
        
        Args:
            jquery_selector: jQuery selector string
            
        Returns:
            str: CSS selector equivalent
        """
        if not self.config.CONVERT_JQUERY_SELECTORS:
            return jquery_selector
        
        css_selector = jquery_selector
        
        # Convert jQuery pseudo-selectors to CSS equivalents
        conversions = {
            ':first': ':first-child',
            ':last': ':last-child',
            ':eq(': ':nth-child(',
            ':even': ':nth-child(even)',
            ':odd': ':nth-child(odd)',
            ':gt(': ':nth-child(n+',  # Approximate
            ':lt(': ':nth-child(-n+',  # Approximate
            ':visible': ':not([hidden])',  # Approximation
            ':hidden': '[hidden]',  # Approximation
            ':checked': ':checked',  # Same
            ':selected': ':checked',  # Close enough for most cases
            ':disabled': ':disabled',  # Same
            ':enabled': ':not(:disabled)',  # CSS equivalent
            ':focus': ':focus'  # Same
        }
        
        for jquery_pseudo, css_pseudo in conversions.items():
            css_selector = css_selector.replace(jquery_pseudo, css_pseudo)
        
        # Handle :contains() pseudo-selector (jQuery specific)
        contains_pattern = r':contains\(["\']([^"\']+)["\']\)'
        css_selector = re.sub(contains_pattern, r'[title*="\1"], [alt*="\1"]', css_selector)
        
        # Handle :has() pseudo-selector (limited browser support)
        has_pattern = r':has\(([^)]+)\)'
        css_selector = re.sub(has_pattern, r':has(\1)', css_selector)
        
        if css_selector != jquery_selector:
            logger.debug(f"Converted jQuery selector '{jquery_selector}' to CSS '{css_selector}'")
        
        return css_selector
    
    def _is_explicit_strategy(self, locator: str) -> bool:
        """
        Check if locator uses explicit strategy syntax.
        
        Args:
            locator: Locator string to check
            
        Returns:
            bool: True if uses explicit strategy (strategy=value)
        """
        if "=" not in locator and ":" not in locator:
            return False
        
        # Check for known strategies before the = or :
        known_strategies = [
            'id', 'name', 'class', 'tag', 'xpath', 'css', 'dom', 
            'link', 'partial link', 'data', 'jquery', 'identifier', 'text'
        ]
        
        separator = "=" if "=" in locator else ":"
        potential_strategy = locator.split(separator, 1)[0].lower().strip()
        
        return potential_strategy in known_strategies
    
    def _is_css_selector(self, locator: str) -> bool:
        """
        Check if locator appears to be a CSS selector.
        
        Args:
            locator: Locator string to check
            
        Returns:
            bool: True if appears to be CSS selector
        """
        # Simple heuristics for CSS selectors
        css_indicators = [
            locator.startswith("#"),  # ID selector
            locator.startswith("."),  # Class selector
            locator.startswith("["),  # Attribute selector
            " > " in locator,         # Child combinator
            " + " in locator,         # Adjacent sibling
            " ~ " in locator,         # General sibling
            ":nth-child(" in locator, # Pseudo-selector
            ":first-child" in locator,
            ":last-child" in locator,
            ":hover" in locator,
            ":focus" in locator
        ]
        
        return any(css_indicators) and not locator.startswith("//")
    
    def get_conversion_stats(self) -> Dict[str, int]:
        """
        Get statistics about locator conversions performed.
        
        Returns:
            dict: Conversion statistics
        """
        # In a real implementation, this would track conversion counts
        return {
            "total_conversions": 0,
            "to_browser_library": 0,
            "to_selenium_library": 0,
            "jquery_conversions": 0,
            "failed_conversions": 0
        }
    
    def validate_locator(self, locator: str, library_type: str) -> Dict[str, bool]:
        """
        Validate locator syntax for a specific library.
        
        Args:
            locator: Locator string to validate
            library_type: Library to validate for ("Browser" or "SeleniumLibrary")
            
        Returns:
            dict: Validation results
        """
        validation = {
            "valid": True,
            "warnings": []
        }
        
        if not locator:
            validation["valid"] = False
            validation["warnings"].append("Empty locator")
            return validation
        
        if library_type == "Browser":
            # Check for SeleniumLibrary-specific syntax that might not work
            if locator.startswith("dom="):
                validation["warnings"].append("DOM locators not supported in Browser Library")
            elif "identifier=" in locator:
                validation["warnings"].append("identifier= strategy not directly supported in Browser Library")
        
        elif library_type == "SeleniumLibrary":
            # Check for Browser Library-specific syntax
            if ">>" in locator:
                validation["warnings"].append("Cascaded selectors (>>) may need conversion for SeleniumLibrary")
            elif locator.startswith("text="):
                validation["warnings"].append("text= selectors need conversion for SeleniumLibrary")
        
        return validation
    
    def get_supported_strategies(self, library_type: str) -> List[str]:
        """
        Get list of supported locator strategies for a library.
        
        Args:
            library_type: Library type ("Browser" or "SeleniumLibrary")
            
        Returns:
            list: List of supported strategy names
        """
        if library_type == "Browser":
            return [
                "css", "xpath", "text", "id", "cascaded", "shadow"
            ]
        elif library_type == "SeleniumLibrary":
            return [
                "id", "name", "identifier", "class", "tag", "xpath", 
                "css", "dom", "link", "partial link", "data", "jquery"
            ]
        else:
            return []