#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Electron Browser Bridge
Provides Selenium-compatible API for Electron WebContents
"""

import os
import time
import base64
import logging
from typing import Optional, Dict, Any, List
from enum import Enum

try:
    import requests
except ImportError:
    requests = None
    print("⚠️ Warning: 'requests' library not found. Install with: pip install requests")


class By:
    """Selenium-compatible By class for element selection"""
    ID = "id"
    NAME = "name"
    CLASS_NAME = "class_name"
    TAG_NAME = "tag_name"
    CSS_SELECTOR = "css_selector"
    XPATH = "xpath"


class ElectronElement:
    """Element wrapper for Electron browser"""
    
    def __init__(self, browser, by: str, value: str, frame_selector: Optional[str] = None):
        self.browser = browser
        self.by = by
        self.value = value
        self.frame_selector = frame_selector
        self._element_data = None
    
    def _ensure_element_data(self):
        """Ensure element data is loaded"""
        if self._element_data is None:
            result = self.browser._find_element(self.by, self.value, frame_selector=self.frame_selector)
            if result and result.get('success'):
                self._element_data = result.get('element')
            else:
                raise Exception(f"Element not found: {self.by}={self.value}")
    
    def click(self):
        """Click the element"""
        return self.browser._click(self.by, self.value, frame_selector=self.frame_selector)
    
    def send_keys(self, text: str):
        """Type text into the element"""
        return self.browser._type(self.by, self.value, text, frame_selector=self.frame_selector)
    
    def clear(self):
        """Clear the element"""
        return self.browser._type(self.by, self.value, "", clear=True, frame_selector=self.frame_selector)
    
    def get_attribute(self, name: str) -> Optional[str]:
        """Get element attribute"""
        self._ensure_element_data()
        if name == 'value':
            return self._element_data.get('value')
        elif name == 'id':
            return self._element_data.get('id')
        elif name == 'class':
            return self._element_data.get('className')
        return None
    
    @property
    def text(self) -> str:
        """Get element text"""
        self._ensure_element_data()
        return self._element_data.get('text', '')
    
    @property
    def tag_name(self) -> str:
        """Get element tag name"""
        self._ensure_element_data()
        return self._element_data.get('tagName', '')


class ElectronWait:
    """Wait utility for Electron browser"""
    
    def __init__(self, browser, timeout: float = 20):
        self.browser = browser
        self.timeout = timeout
    
    def until(self, condition, message: str = ""):
        """Wait until condition is met"""
        if hasattr(condition, '__call__'):
            # Custom condition function
            start_time = time.time()
            while time.time() - start_time < self.timeout:
                try:
                    result = condition(self.browser)
                    if result:
                        return result
                except Exception:
                    pass
                time.sleep(0.1)
            raise TimeoutError(f"Timeout waiting for condition: {message}")
        else:
            # ExpectedConditions-like object
            if hasattr(condition, 'locator'):
                by, value = condition.locator
                result = self.browser._wait_for_element(by, value, self.timeout)
                if result and result.get('success'):
                    return ElectronElement(self.browser, by, value)
                else:
                    error_msg = result.get('error', 'Unknown error') if result else 'No result'
                    raise TimeoutError(f"Timeout waiting for element: {error_msg}")
        raise TimeoutError(f"Unknown condition type: {message}")


class ExpectedConditions:
    """Selenium-compatible expected conditions"""
    
    @staticmethod
    def presence_of_element_located(locator):
        """Wait for element to be present"""
        class Condition:
            def __init__(self, locator):
                self.locator = locator
        return Condition(locator)
    
    @staticmethod
    def element_to_be_clickable(locator):
        """Wait for element to be clickable"""
        class Condition:
            def __init__(self, locator):
                self.locator = locator
        return Condition(locator)


class SwitchTo:
    """Window and alert switching utility"""
    
    def __init__(self, browser):
        self.browser = browser
        self._alert = None
    
    def window(self, window_handle):
        """Switch to a different window"""
        if isinstance(window_handle, int):
            window_index = window_handle
        else:
            # Find window index by handle
            window_index = self.browser.window_handles.index(window_handle) if window_handle in self.browser.window_handles else 0
        
        result = self.browser._switch_window(window_index)
        if result and result.get('success'):
            self.browser.current_window_index = window_index
            return True
        return False
    
    @property
    def alert(self):
        """Get alert handler"""
        if self._alert is None:
            self._alert = ElectronAlert(self.browser)
        return self._alert
    
    def default_content(self):
        """Switch to top-level document (no-op for single WebContents)"""
        return True


class ElectronAlert:
    """Alert handler"""
    
    def __init__(self, browser):
        self.browser = browser
    
    def accept(self):
        """Accept the alert"""
        return self.browser._handle_alert('accept')
    
    def dismiss(self):
        """Dismiss the alert"""
        return self.browser._handle_alert('dismiss')
    
    @property
    def text(self) -> str:
        """Get alert text (not fully supported)"""
        # Note: Getting alert text requires more complex implementation
        return ""


class ElectronBrowser:
    """Electron browser wrapper with Selenium-compatible API"""
    
    def __init__(self, job_id: str, api_url: str, headless: bool = True):
        self.job_id = job_id
        self.api_url = api_url.rstrip('/')
        self.headless = headless
        self.current_window_index = 0
        self._window_handles_cache = None  # Cache for window handles
        self.logger = logging.getLogger(f"ElectronBrowser-{job_id}")
        
        if requests is None:
            raise ImportError("requests library is required. Install with: pip install requests")
        
        # Create browser session
        self._create_session()
    
    @property
    def window_handles(self):
        """Get window handles (Selenium compatibility) - dynamically queries server"""
        try:
            endpoint = f"/browser/{self.job_id}/get-window-handles"
            result = self._request('POST', endpoint)
            if result.get('success'):
                handles = result.get('handles', [0])
                self._window_handles_cache = handles
                return handles
            else:
                # Fallback to cache or default
                return self._window_handles_cache if self._window_handles_cache else [0]
        except Exception as e:
            self.logger.warning(f"Failed to get window handles: {e}")
            # Fallback to cache or default
            return self._window_handles_cache if self._window_handles_cache else [0]
    
    @property
    def current_window_handle(self):
        """Get current window handle (Selenium compatibility)"""
        handles = self.window_handles
        if handles and self.current_window_index < len(handles):
            return handles[self.current_window_index]
        return handles[0] if handles else None
    
    @classmethod
    def create(cls, job_id: str, headless: bool = True, user_data_dir: Optional[str] = None, 
               preferences: Optional[Dict] = None, logger: Optional[logging.Logger] = None):
        """Create a new Electron browser instance"""
        # Get API URL from environment
        api_url = os.environ.get('ELECTRON_BROWSER_API_URL', 'http://127.0.0.1:9234')
        
        browser = cls(job_id, api_url, headless)
        if logger:
            browser.logger = logger
        
        return browser
    
    def _create_session(self):
        """Create browser session in Electron"""
        url = f"{self.api_url}/browser/create"
        data = {
            'jobId': self.job_id,
            'headless': self.headless,
        }
        
        try:
            response = requests.post(url, json=data, timeout=10)
            response.raise_for_status()
            result = response.json()
            if not result.get('success'):
                raise Exception(f"Failed to create browser session: {result.get('error')}")
            self.logger.info(f"✅ Browser session created: {self.job_id}")
        except Exception as e:
            self.logger.error(f"❌ Failed to create browser session: {e}")
            raise
    
    def _request(self, method: str, endpoint: str, data: Optional[Dict] = None) -> Dict:
        """Make HTTP request to Electron API"""
        url = f"{self.api_url}{endpoint}"
        try:
            if method == 'POST':
                response = requests.post(url, json=data, timeout=30)
            elif method == 'DELETE':
                response = requests.delete(url, timeout=10)
            else:
                raise ValueError(f"Unsupported method: {method}")
            
            response.raise_for_status()
            return response.json()
        except requests.exceptions.RequestException as e:
            self.logger.error(f"❌ API request failed: {e}")
            return {'success': False, 'error': str(e)}
    
    def _find_element(self, by: str, value: str, frame_selector: Optional[str] = None) -> Dict:
        """Find element"""
        endpoint = f"/browser/{self.job_id}/find-element"
        payload = {'by': by, 'value': value}
        if frame_selector:
            payload['frameSelector'] = frame_selector
        return self._request('POST', endpoint, payload)
    
    def _click(self, by: str, value: str, frame_selector: Optional[str] = None) -> Dict:
        """Click element"""
        endpoint = f"/browser/{self.job_id}/click"
        payload = {'by': by, 'value': value}
        if frame_selector:
            payload['frameSelector'] = frame_selector
        return self._request('POST', endpoint, payload)
    
    def _type(self, by: str, value: str, text: str, clear: bool = False, frame_selector: Optional[str] = None) -> Dict:
        """Type text into element"""
        endpoint = f"/browser/{self.job_id}/type"
        payload = {'by': by, 'value': value, 'text': text, 'clear': clear}
        if frame_selector:
            payload['frameSelector'] = frame_selector
        return self._request('POST', endpoint, payload)
    
    def _wait_for_element(self, by: str, value: str, timeout: float = 20, frame_selector: Optional[str] = None) -> Dict:
        """Wait for element"""
        endpoint = f"/browser/{self.job_id}/wait-for-element"
        payload = {'by': by, 'value': value, 'timeout': int(timeout * 1000)}
        if frame_selector:
            payload['frameSelector'] = frame_selector
        result = self._request('POST', endpoint, payload)
        if not result.get('success'):
            # Extract error message for better debugging
            error_msg = result.get('error', 'Unknown error')
            self.logger.warning(f"⚠️ Wait for element failed: {error_msg}")
        return result
    
    def _switch_window(self, window_index: int) -> Dict:
        """Switch window"""
        endpoint = f"/browser/{self.job_id}/switch-window"
        return self._request('POST', endpoint, {'windowIndex': window_index})
    
    def _handle_alert(self, action: str) -> Dict:
        """Handle alert"""
        endpoint = f"/browser/{self.job_id}/alert"
        return self._request('POST', endpoint, {'action': action})
    
    # Public API (Selenium-compatible)
    
    def get(self, url: str):
        """Navigate to URL"""
        endpoint = f"/browser/{self.job_id}/navigate"
        result = self._request('POST', endpoint, {'url': url})
        if not result.get('success'):
            raise Exception(f"Navigation failed: {result.get('error')}")
    
    def find_element(self, by: str, value: str, frame_selector: Optional[str] = None) -> ElectronElement:
        """Find single element"""
        result = self._find_element(by, value, frame_selector=frame_selector)
        if result.get('success'):
            return ElectronElement(self, by, value, frame_selector=frame_selector)
        else:
            raise Exception(f"Element not found: {result.get('error')}")
    
    def find_elements(self, by: str, value: str, frame_selector: Optional[str] = None) -> List[ElectronElement]:
        """Find multiple elements (returns list with single element for now)"""
        result = self._find_element(by, value, frame_selector=frame_selector)
        if result.get('success'):
            return [ElectronElement(self, by, value, frame_selector=frame_selector)]
        else:
            return []
    
    @property
    def current_url(self) -> str:
        """Get current URL"""
        endpoint = f"/browser/{self.job_id}/get-url"
        result = self._request('POST', endpoint)
        if result.get('success'):
            return result.get('url', '')
        return ''
    
    @property
    def title(self) -> str:
        """Get page title"""
        endpoint = f"/browser/{self.job_id}/get-title"
        result = self._request('POST', endpoint)
        if result.get('success'):
            return result.get('title', '')
        return ''
    
    def execute_script(self, script: str, frame_selector: Optional[str] = None) -> Any:
        """Execute JavaScript"""
        endpoint = f"/browser/{self.job_id}/execute-script"
        payload = {'script': script}
        if frame_selector:
            payload['frameSelector'] = frame_selector
        result = self._request('POST', endpoint, payload)
        if result.get('success'):
            return result.get('result')
        else:
            raise Exception(f"Script execution failed: {result.get('error')}")
    
    def execute_cdp_cmd(self, command: str, params: Dict) -> Dict:
        """Execute Chrome DevTools Protocol command"""
        if command == "Page.printToPDF":
            endpoint = f"/browser/{self.job_id}/print-pdf"
            result = self._request('POST', endpoint, params)
            if result.get('success'):
                return {'data': result.get('data')}
            else:
                raise Exception(f"PDF generation failed: {result.get('error')}")
        else:
            raise NotImplementedError(f"CDP command not supported: {command}")
    
    def quit(self):
        """Close browser session"""
        endpoint = f"/browser/{self.job_id}"
        self._request('DELETE', endpoint)
        self.logger.info(f"✅ Browser session closed: {self.job_id}")
    
    def close(self):
        """Alias for quit"""
        self.quit()
    
    @property
    def switch_to(self) -> SwitchTo:
        """Get switch_to utility"""
        if not hasattr(self, '_switch_to'):
            self._switch_to = SwitchTo(self)
        return self._switch_to
    
    def __enter__(self):
        """Context manager entry"""
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit"""
        self.quit()


# Compatibility aliases
EC = ExpectedConditions

