from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, WebDriverException
from webdriver_manager.chrome import ChromeDriverManager
import asyncio
import logging
from typing import Optional, List, Dict, Any
from contextlib import asynccontextmanager
import random
import time

logger = logging.getLogger(__name__)

class SeleniumManager:
    def __init__(self, headless: bool = True, proxy: Optional[str] = None):
        self.headless = headless
        self.proxy = proxy
        self.driver: Optional[webdriver.Chrome] = None
        self.wait: Optional[WebDriverWait] = None
        
    def _get_chrome_options(self) -> Options:
        """Get Chrome options for the driver"""
        options = Options()
        
        if self.headless:
            options.add_argument('--headless')
        
        # Performance and security options
        options.add_argument('--no-sandbox')
        options.add_argument('--disable-dev-shm-usage')
        options.add_argument('--disable-gpu')
        options.add_argument('--disable-extensions')
        options.add_argument('--disable-plugins')
        options.add_argument('--disable-images')
        options.add_argument('--disable-javascript')  # Can be removed if JS is needed
        options.add_argument('--window-size=1920,1080')
        
        # User agent rotation
        user_agents = [
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
            'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
        ]
        options.add_argument(f'--user-agent={random.choice(user_agents)}')
        
        # Proxy configuration
        if self.proxy:
            options.add_argument(f'--proxy-server={self.proxy}')
        
        # Additional anti-detection measures
        options.add_experimental_option("excludeSwitches", ["enable-automation"])
        options.add_experimental_option('useAutomationExtension', False)
        
        return options
    
    async def start_driver(self):
        """Start the Chrome driver"""
        try:
            options = self._get_chrome_options()
            service = Service(ChromeDriverManager().install())
            
            self.driver = webdriver.Chrome(service=service, options=options)
            self.driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
            
            self.wait = WebDriverWait(self.driver, 10)
            
            logger.info("Chrome driver started successfully")
            
        except Exception as e:
            logger.error(f"Error starting Chrome driver: {e}")
            raise
    
    async def stop_driver(self):
        """Stop the Chrome driver"""
        if self.driver:
            try:
                self.driver.quit()
                logger.info("Chrome driver stopped")
            except Exception as e:
                logger.error(f"Error stopping Chrome driver: {e}")
    
    async def navigate_to(self, url: str, wait_for_element: Optional[str] = None):
        """Navigate to a URL"""
        try:
            self.driver.get(url)
            
            # Random delay to mimic human behavior
            await asyncio.sleep(random.uniform(1, 3))
            
            # Wait for specific element if provided
            if wait_for_element:
                self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, wait_for_element)))
            
            logger.debug(f"Navigated to: {url}")
            
        except TimeoutException:
            logger.warning(f"Timeout waiting for element: {wait_for_element}")
        except Exception as e:
            logger.error(f"Error navigating to {url}: {e}")
            raise
    
    async def find_elements(self, selector: str, timeout: int = 10) -> List[Any]:
        """Find elements by CSS selector"""
        try:
            wait = WebDriverWait(self.driver, timeout)
            elements = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, selector)))
            return elements
        except TimeoutException:
            logger.warning(f"No elements found for selector: {selector}")
            return []
        except Exception as e:
            logger.error(f"Error finding elements: {e}")
            return []
    
    async def find_element(self, selector: str, timeout: int = 10) -> Optional[Any]:
        """Find single element by CSS selector"""
        try:
            wait = WebDriverWait(self.driver, timeout)
            element = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, selector)))
            return element
        except TimeoutException:
            logger.warning(f"Element not found for selector: {selector}")
            return None
        except Exception as e:
            logger.error(f"Error finding element: {e}")
            return None
    
    async def scroll_page(self, pixels: int = 1000):
        """Scroll the page"""
        try:
            self.driver.execute_script(f"window.scrollBy(0, {pixels});")
            await asyncio.sleep(random.uniform(0.5, 1.5))
        except Exception as e:
            logger.error(f"Error scrolling page: {e}")
    
    async def wait_for_page_load(self):
        """Wait for page to fully load"""
        try:
            self.wait.until(lambda driver: driver.execute_script("return document.readyState") == "complete")
        except Exception as e:
            logger.error(f"Error waiting for page load: {e}")
    
    @asynccontextmanager
    async def driver_context(self):
        """Context manager for driver lifecycle"""
        await self.start_driver()
        try:
            yield self
        finally:
            await self.stop_driver()
