from selenium import webdriver
from selenium.webdriver.chrome.options import Options
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, NoSuchElementException
import os
import time
import logging
import requests
import random
import re
import uuid
import subprocess
import shutil
from urllib.parse import urlparse, parse_qs

class BrowserAutomationDownloader:
    def __init__(self, download_path='downloads'):
        self.download_path = download_path
        
        # Ensure download directory exists
        os.makedirs(download_path, exist_ok=True)
        
        # Configure logging
        logging.basicConfig(
            format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
            level=logging.INFO
        )
        self.logger = logging.getLogger(__name__)
        
        # User agents for rotation
        self.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/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15',
            'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0',
            'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1'
        ]
    
    def get_random_user_agent(self):
        """Get a random user agent from the list"""
        return random.choice(self.user_agents)
    
    def get_youtube_video_id(self, url):
        """Extract YouTube video ID from URL"""
        if 'youtu.be' in url:
            # Short URL format: https://youtu.be/VIDEO_ID
            path = urlparse(url).path
            return path.strip('/').split('?')[0]
        else:
            # Regular URL format: https://www.youtube.com/watch?v=VIDEO_ID
            parsed_url = urlparse(url)
            query_params = parse_qs(parsed_url.query)
            return query_params.get('v', [''])[0]
    
    def setup_chrome_driver(self):
        """Set up Chrome WebDriver with appropriate options"""
        try:
            chrome_options = Options()
            chrome_options.add_argument("--headless")
            chrome_options.add_argument("--no-sandbox")
            chrome_options.add_argument("--disable-dev-shm-usage")
            chrome_options.add_argument(f"user-agent={self.get_random_user_agent()}")
            chrome_options.add_argument("--disable-blink-features=AutomationControlled")
            chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
            chrome_options.add_experimental_option('useAutomationExtension', False)
            
            # Set download preferences
            prefs = {
                "download.default_directory": os.path.abspath(self.download_path),
                "download.prompt_for_download": False,
                "download.directory_upgrade": True,
                "safebrowsing.enabled": False
            }
            chrome_options.add_experimental_option("prefs", prefs)
            
            driver = webdriver.Chrome(options=chrome_options)
            
            # Execute CDP commands to mask automation
            driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
                "source": """
                Object.defineProperty(navigator, 'webdriver', {
                    get: () => undefined
                });
                """
            })
            
            return driver
        except Exception as e:
            self.logger.error(f"Error setting up Chrome driver: {str(e)}")
            raise
    
    def download_with_y2mate(self, url, audio_only=False):
        """Download YouTube video using y2mate.com with browser automation"""
        driver = None
        try:
            video_id = self.get_youtube_video_id(url)
            if not video_id:
                return {'success': False, 'error': 'Could not extract YouTube video ID'}
            
            self.logger.info(f"Attempting to download YouTube video with ID: {video_id} using y2mate browser automation")
            
            # Initialize Chrome driver
            driver = self.setup_chrome_driver()
            
            # Navigate to y2mate
            y2mate_url = "https://www.y2mate.com/en68"
            driver.get(y2mate_url)
            
            # Wait for page to load
            WebDriverWait(driver, 10).until(
                EC.presence_of_element_located((By.ID, "txt-url"))
            )
            
            # Input YouTube URL
            url_input = driver.find_element(By.ID, "txt-url")
            url_input.clear()
            url_input.send_keys(url)
            
            # Click search button
            search_button = driver.find_element(By.ID, "btn-submit")
            search_button.click()
            
            # Wait for search results
            WebDriverWait(driver, 20).until(
                EC.presence_of_element_located((By.CSS_SELECTOR, ".thumbnail.cover"))
            )
            
            # Get video title
            title_element = WebDriverWait(driver, 10).until(
                EC.presence_of_element_located((By.CSS_SELECTOR, ".caption > b"))
            )
            title = title_element.text.strip()
            
            # Select format
            if audio_only:
                # Select MP3 tab
                mp3_tab = WebDriverWait(driver, 10).until(
                    EC.element_to_be_clickable((By.LINK_TEXT, "MP3"))
                )
                mp3_tab.click()
                
                # Wait for MP3 options to load
                WebDriverWait(driver, 10).until(
                    EC.presence_of_element_located((By.CSS_SELECTOR, "a[data-ftype='mp3']"))
                )
                
                # Select 128kbps MP3
                download_button = WebDriverWait(driver, 10).until(
                    EC.element_to_be_clickable((By.CSS_SELECTOR, "a[data-ftype='mp3'][data-fquality='128']"))
                )
            else:
                # Select MP4 tab (default)
                # Try to find the highest quality available
                for quality in ['1080p', '720p', '480p', '360p']:
                    try:
                        download_button = WebDriverWait(driver, 5).until(
                            EC.element_to_be_clickable((By.CSS_SELECTOR, f"a[data-ftype='mp4'][data-fquality='{quality}']"))
                        )
                        break
                    except TimeoutException:
                        continue
                else:
                    # If no specific quality found, try any MP4 button
                    download_button = WebDriverWait(driver, 10).until(
                        EC.element_to_be_clickable((By.CSS_SELECTOR, "a[data-ftype='mp4']"))
                    )
            
            # Click download button to start conversion
            download_button.click()
            
            # Wait for conversion to complete and download link to appear
            WebDriverWait(driver, 30).until(
                EC.element_to_be_clickable((By.CSS_SELECTOR, "a.btn.btn-success"))
            )
            
            # Get download link
            download_link = driver.find_element(By.CSS_SELECTOR, "a.btn.btn-success")
            download_url = download_link.get_attribute("href")
            
            if not download_url:
                return {'success': False, 'error': 'Could not get download URL'}
            
            # Download the file using requests
            ext = 'mp3' if audio_only else 'mp4'
            output_file = os.path.join(self.download_path, f"youtube_{video_id}.{ext}")
            
            headers = {
                'User-Agent': self.get_random_user_agent(),
                'Referer': y2mate_url
            }
            
            response = requests.get(download_url, headers=headers, stream=True, timeout=30)
            if response.status_code == 200:
                with open(output_file, 'wb') as f:
                    for chunk in response.iter_content(chunk_size=8192):
                        f.write(chunk)
                
                # Verify file was downloaded successfully
                if os.path.exists(output_file) and os.path.getsize(output_file) > 0:
                    return {
                        'success': True,
                        'file_path': output_file,
                        'title': title,
                        'format': ext
                    }
                else:
                    return {'success': False, 'error': 'Downloaded file is empty or does not exist'}
            else:
                return {'success': False, 'error': f'Failed to download file, status code: {response.status_code}'}
            
        except Exception as e:
            self.logger.error(f"Error in y2mate browser automation: {str(e)}")
            return {'success': False, 'error': str(e)}
        finally:
            if driver:
                driver.quit()
    
    def download_with_savefrom(self, url, audio_only=False):
        """Download video using savefrom.net with browser automation"""
        driver = None
        try:
            self.logger.info(f"Attempting to download video from {url} using savefrom browser automation")
            
            # Initialize Chrome driver
            driver = self.setup_chrome_driver()
            
            # Navigate to savefrom.net
            savefrom_url = "https://savefrom.net/"
            driver.get(savefrom_url)
            
            # Wait for page to load
            WebDriverWait(driver, 10).until(
                EC.presence_of_element_located((By.ID, "sf_url"))
            )
            
            # Input video URL
            url_input = driver.find_element(By.ID, "sf_url")
            url_input.clear()
            url_input.send_keys(url)
            
            # Click submit button
            submit_button = driver.find_element(By.ID, "sf_submit")
            submit_button.click()
            
            # Wait for results
            WebDriverWait(driver, 30).until(
                EC.presence_of_element_located((By.CSS_SELECTOR, ".def-btn-box"))
            )
            
            # Get video title
            try:
                title_element = driver.find_element(By.CSS_SELECTOR, ".title")
                title = title_element.text.strip()
            except NoSuchElementException:
                title = f"Video {uuid.uuid4().hex[:8]}"
            
            # Select format
            if audio_only:
                # Try to find MP3 download button
                try:
                    download_button = WebDriverWait(driver, 10).until(
                        EC.element_to_be_clickable((By.CSS_SELECTOR, "a.link[data-type='mp3']"))
                    )
                except TimeoutException:
                    # If MP3 not available, use default download button
                    download_button = WebDriverWait(driver, 10).until(
                        EC.element_to_be_clickable((By.CSS_SELECTOR, ".def-btn-box a.link"))
                    )
            else:
                # Try to find the highest quality MP4
                try:
                    quality_buttons = driver.find_elements(By.CSS_SELECTOR, "a.link[data-type='mp4']")
                    if quality_buttons:
                        download_button = quality_buttons[0]  # Highest quality is usually first
                    else:
                        # If no specific MP4 button, use default download button
                        download_button = driver.find_element(By.CSS_SELECTOR, ".def-btn-box a.link")
                except NoSuchElementException:
                    # If no specific buttons found, use default download button
                    download_button = driver.find_element(By.CSS_SELECTOR, ".def-btn-box a.link")
            
            # Get download URL
            download_url = download_button.get_attribute("href")
            
            if not download_url:
                return {'success': False, 'error': 'Could not get download URL'}
            
            # Determine file extension
            if audio_only:
                ext = 'mp3'
            else:
                ext = 'mp4'
                # Try to get actual extension from button text
                try:
                    format_text = download_button.text.strip().lower()
                    if 'mp4' in format_text:
                        ext = 'mp4'
                    elif 'webm' in format_text:
                        ext = 'webm'
                    elif 'mov' in format_text:
                        ext = 'mov'
                except:
                    pass
            
            # Generate output filename
            if 'youtube' in url:
                video_id = self.get_youtube_video_id(url)
                output_file = os.path.join(self.download_path, f"youtube_{video_id}.{ext}")
            else:
                output_file = os.path.join(self.download_path, f"video_{uuid.uuid4().hex[:8]}.{ext}")
            
            # Download the file using requests
            headers = {
                'User-Agent': self.get_random_user_agent(),
                'Referer': savefrom_url
            }
            
            response = requests.get(download_url, headers=headers, stream=True, timeout=30)
            if response.status_code == 200:
                with open(output_file, 'wb') as f:
                    for chunk in response.iter_content(chunk_size=8192):
                        f.write(chunk)
                
                # Verify file was downloaded successfully
                if os.path.exists(output_file) and os.path.getsize(output_file) > 0:
                    # Convert to mp3 if audio_only and not already mp3
                    if audio_only and ext != 'mp3':
                        mp3_file = os.path.join(self.download_path, f"{os.path.splitext(os.path.basename(output_file))[0]}.mp3")
                        cmd = [
                            'ffmpeg', '-i', output_file, 
                            '-vn', '-ab', '128k', '-ar', '44100', '-y', mp3_file
                        ]
                        subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                        os.remove(output_file)
                        output_file = mp3_file
                        ext = 'mp3'
                    
                    return {
                        'success': True,
                        'file_path': output_file,
                        'title': title,
                        'format': ext
                    }
                else:
                    return {'success': False, 'error': 'Downloaded file is empty or does not exist'}
            else:
                return {'success': False, 'error': f'Failed to download file, status code: {response.status_code}'}
            
        except Exception as e:
            self.logger.error(f"Error in savefrom browser automation: {str(e)}")
            return {'success': False, 'error': str(e)}
        finally:
            if driver:
                driver.quit()
    
    def download_with_ssyoutube(self, url, audio_only=False):
        """Download YouTube video using ssyoutube.com with browser automation"""
        driver = None
        try:
            video_id = self.get_youtube_video_id(url)
            if not video_id:
                return {'success': False, 'error': 'Could not extract YouTube video ID'}
            
            self.logger.info(f"Attempting to download YouTube video with ID: {video_id} using ssyoutube browser automation")
            
            # Convert to ssyoutube URL
            if 'youtube.com' in url:
                ss_url = url.replace('youtube.com', 'ssyoutube.com')
            elif 'youtu.be' in url:
                ss_url = f"https://ssyoutube.com/watch?v={video_id}"
            else:
                ss_url = f"https://ssyoutube.com/watch?v={video_id}"
            
            # Initialize Chrome driver
            driver = self.setup_chrome_driver()
            
            # Navigate to ssyoutube
            driver.get(ss_url)
            
            # Wait for page to load and process the video
            WebDriverWait(driver, 30).until(
                EC.presence_of_element_located((By.CSS_SELECTOR, ".download-btn, .download-link"))
            )
            
            # Get video title
            try:
                title_element = driver.find_element(By.CSS_SELECTOR, "h1.title, .video-title")
                title = title_element.text.strip()
            except NoSuchElementException:
                title = f"YouTube Video {video_id}"
            
            # Select format
            if audio_only:
                # Try to find MP3 download button
                try:
                    download_buttons = driver.find_elements(By.CSS_SELECTOR, "a[href*='mp3']")
                    if download_buttons:
                        download_button = download_buttons[0]
                    else:
                        # If no MP3 button found, try alternative selectors
                        download_button = driver.find_element(By.CSS_SELECTOR, ".download-btn[data-format='mp3'], .download-link[data-format='mp3']")
                except NoSuchElementException:
                    return {'success': False, 'error': 'Could not find MP3 download button'}
            else:
                # Try to find the highest quality MP4
                try:
                    # Look for quality indicators in download buttons
                    quality_pattern = re.compile(r'(\d+)p')
                    download_buttons = driver.find_elements(By.CSS_SELECTOR, "a[href*='mp4']")
                    
                    # Sort buttons by quality (highest first)
                    def get_quality(button):
                        text = button.text.strip()
                        match = quality_pattern.search(text)
                        if match:
                            return int(match.group(1))
                        return 0
                    
                    sorted_buttons = sorted(download_buttons, key=get_quality, reverse=True)
                    
                    if sorted_buttons:
                        download_button = sorted_buttons[0]
                    else:
                        # If no quality-specific buttons found, use first MP4 button
                        download_button = driver.find_element(By.CSS_SELECTOR, ".download-btn[href*='mp4'], .download-link[href*='mp4']")
                except NoSuchElementException:
                    return {'success': False, 'error': 'Could not find MP4 download button'}
            
            # Get download URL
            download_url = download_button.get_attribute("href")
            
            if not download_url:
                return {'success': False, 'error': 'Could not get download URL'}
            
            # Determine file extension
            ext = 'mp3' if audio_only else 'mp4'
            
            # Generate output filename
            output_file = os.path.join(self.download_path, f"youtube_{video_id}.{ext}")
            
            # Download the file using requests
            headers = {
                'User-Agent': self.get_random_user_agent(),
                'Referer': ss_url
            }
            
            response = requests.get(download_url, headers=headers, stream=True, timeout=30)
            if response.status_code == 200:
                with open(output_file, 'wb') as f:
                    for chunk in response.iter_content(chunk_size=8192):
                        f.write(chunk)
                
                # Verify file was downloaded successfully
                if os.path.exists(output_file) and os.path.getsize(output_file) > 0:
                    return {
                        'success': True,
                        'file_path': output_file,
                        'title': title,
                        'format': ext
                    }
                else:
                    return {'success': False, 'error': 'Downloaded file is empty or does not exist'}
            else:
                return {'success': False, 'error': f'Failed to download file, status code: {response.status_code}'}
            
        except Exception as e:
            self.logger.error(f"Error in ssyoutube browser automation: {str(e)}")
            return {'success': False, 'error': str(e)}
        finally:
            if driver:
                driver.quit()
    
    def download_youtube_with_browser(self, url, audio_only=False):
        """Aggressively try multiple browser automation methods to download YouTube video"""
        self.logger.info(f"Starting aggressive browser-based YouTube download for {url}")
        
        # Try all methods in sequence until one succeeds
        methods = [
            ('y2mate', self.download_with_y2mate),
            ('savefrom', self.download_with_savefrom),
            ('ssyoutube', self.download_with_ssyoutube)
        ]
        
        all_errors = []
        
        # Try different URL formats
        urls_to_try = [url]
        video_id = self.get_youtube_video_id(url)
        if video_id:
            # Add alternative URL formats
            if 'youtu.be' in url:
                urls_to_try.append(f"https://www.youtube.com/watch?v={video_id}")
            else:
                urls_to_try.append(f"https://youtu.be/{video_id}")
            
            # Add URL with timestamp parameter
            urls_to_try.append(f"https://www.youtube.com/watch?v={video_id}&t=0s")
            
            # Add URL with additional parameters
            urls_to_try.append(f"https://www.youtube.com/watch?v={video_id}&feature=youtu.be")
        
        # Try each URL with each method
        for current_url in urls_to_try:
            self.logger.info(f"Trying URL format: {current_url}")
            
            for method_name, method_func in methods:
                try:
                    self.logger.info(f"Attempting download with method: {method_name}")
                    result = method_func(current_url, audio_only)
                    
                    if result['success']:
                        self.logger.info(f"Successfully downloaded with method: {method_name}")
                        return result
                    else:
                        all_errors.append(f"{method_name}: {result.get('error', 'Unknown error')}")
                        self.logger.warning(f"Method {method_name} failed: {result.get('error', 'Unknown error')}")
                except Exception as e:
                    all_errors.append(f"{method_name}: {str(e)}")
                    self.logger.error(f"Exception in method {method_name}: {str(e)}")
                
                # Add delay between attempts
                time.sleep(random.uniform(1, 3))
        
        # If all methods failed, return error
        error_summary = "; ".join(all_errors)
        self.logger.error(f"All browser download methods failed: {error_summary}")
        return {'success': False, 'error': f"Failed to extract video information after multiple browser-based attempts"}
    
    def compress_video(self, input_file, target_size_mb=10, output_file=None):
        """Compress video to target size in MB"""
        try:
            self.logger.info(f"Compressing video: {input_file} to target size: {target_size_mb}MB")
            
            # Create output file if not provided
            if output_file is None:
                file_name, file_ext = os.path.splitext(input_file)
                output_file = f"{file_name}_compressed{file_ext}"
            
            # Check if ffmpeg is installed
            try:
                subprocess.run(['ffmpeg', '-version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
            except (subprocess.SubprocessError, FileNotFoundError):
                self.logger.error("ffmpeg not found. Please install ffmpeg.")
                return input_file
            
            # Get video duration
            cmd = ['ffprobe', '-v', 'error', '-show_entries', 'format=duration', 
                   '-of', 'default=noprint_wrappers=1:nokey=1', input_file]
            result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
            
            if result.returncode != 0:
                self.logger.error(f"Error getting video duration: {result.stderr}")
                return input_file
                
            duration = float(result.stdout.strip())
            
            # Calculate target bitrate (80% for video, 20% for audio)
            target_size_bits = target_size_mb * 8 * 1024 * 1024
            bitrate = int(target_size_bits / (duration * 1.2))  # 1.2 factor for overhead
            
            # Compress video
            cmd = [
                'ffmpeg', '-i', input_file, 
                '-b:v', str(bitrate), 
                '-maxrate', str(bitrate), 
                '-bufsize', str(bitrate*2),
                '-c:a', 'aac', '-b:a', '128k',  # Compress audio too
                '-y', output_file
            ]
            
            self.logger.info(f"Running ffmpeg command: {' '.join(cmd)}")
            result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            
            if result.returncode != 0:
                self.logger.error(f"Error compressing video: {result.stderr}")
                return input_file
            
            # Verify the compressed file exists and is smaller
            if os.path.exists(output_file):
                original_size = os.path.getsize(input_file) / (1024 * 1024)  # MB
                compressed_size = os.path.getsize(output_file) / (1024 * 1024)  # MB
                
                self.logger.info(f"Original size: {original_size:.2f}MB, Compressed size: {compressed_size:.2f}MB")
                
                if compressed_size < original_size:
                    return output_file
                else:
                    self.logger.warning("Compressed file is not smaller than original. Using original file.")
                    os.remove(output_file)
                    return input_file
            else:
                self.logger.error("Compressed file was not created")
                return input_file
                
        except Exception as e:
            self.logger.error(f"Error in compress_video: {str(e)}")
            return input_file  # Return original file if compression fails
    
    def download(self, url, audio_only=False, compress=True, target_size_mb=10):
        """Main download method using browser automation"""
        self.logger.info(f"Starting browser-based download for {url}, audio_only={audio_only}, compress={compress}")
        
        # For YouTube videos, use the specialized method
        if 'youtube.com' in url or 'youtu.be' in url:
            result = self.download_youtube_with_browser(url, audio_only)
        else:
            # For other URLs, use savefrom
            result = self.download_with_savefrom(url, audio_only)
        
        # If download was successful and it's a video (not audio) and compression is enabled
        if result['success'] and not audio_only and compress:
            file_path = result['file_path']
            file_size_mb = os.path.getsize(file_path) / (1024 * 1024)
            
            # Only compress if file is larger than target size
            if file_size_mb > target_size_mb:
                self.logger.info(f"File size ({file_size_mb:.2f}MB) exceeds target size ({target_size_mb}MB). Compressing...")
                compressed_path = self.compress_video(file_path, target_size_mb)
                
                # Update file path if compression was successful
                if compressed_path != file_path:
                    result['file_path'] = compressed_path
                    result['compressed'] = True
                    # Get new file size
                    new_size_mb = os.path.getsize(compressed_path) / (1024 * 1024)
                    self.logger.info(f"Compression complete. New size: {new_size_mb:.2f}MB")
                else:
                    result['compressed'] = False
            else:
                self.logger.info(f"File size ({file_size_mb:.2f}MB) is already below target size ({target_size_mb}MB). Skipping compression.")
                result['compressed'] = False
        
        return result

# For backward compatibility with existing code
class VideoDownloader(BrowserAutomationDownloader):
    pass
