import os
import logging
import subprocess
import tempfile
import uuid
import shutil
import time
import random
import re
import json
import requests
from urllib.parse import urlparse, parse_qs

class SimpleVideoDownloader:
    def __init__(self, download_path='downloads', public_path=None, public_url=None):
        self.download_path = download_path
        self.public_path = public_path
        self.public_url = public_url
        
        # 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_platform(self, url):
        """Determine the platform from the URL"""
        if 'youtube.com' in url or 'youtu.be' in url:
            return 'youtube'
        elif 'facebook.com' in url or 'fb.com' in url or 'fb.watch' in url:
            return 'facebook'
        elif 'instagram.com' in url:
            return 'instagram'
        elif 'tiktok.com' in url:
            return 'tiktok'
        else:
            return 'generic'
    
    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 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_with_curl_ytdlp(self, url, audio_only=False):
        """Download video using curl to execute yt-dlp command"""
        try:
            self.logger.info(f"Attempting to download {url} with curl + yt-dlp")
            
            # Generate a unique output filename
            platform = self.get_platform(url)
            unique_id = uuid.uuid4().hex[:8]
            ext = 'mp3' if audio_only else 'mp4'
            output_file = os.path.join(self.download_path, f"{platform}_{unique_id}.{ext}")
            
            # Prepare yt-dlp options
            format_option = 'bestaudio' if audio_only else 'best'
            
            # Construct the curl command that will execute yt-dlp
            curl_cmd = [
                'curl', '-s', '-L', '-o', '/dev/null',  # Silent mode, follow redirects, discard output
                '-w', '%{url_effective}',  # Output the final URL after redirects
                url
            ]
            
            # Execute curl to get the effective URL (after redirects)
            effective_url = subprocess.check_output(curl_cmd, stderr=subprocess.PIPE).decode('utf-8').strip()
            
            # Prepare yt-dlp command with all possible options to bypass restrictions
            ytdlp_cmd = [
                'yt-dlp',
                '--no-check-certificate',
                '--force-generic-extractor',
                '--geo-bypass',
                '--no-warnings',
                '--ignore-errors',
                '--no-playlist',
                '-f', format_option,
                '-o', output_file
            ]
            
            # Add post-processing for audio if needed
            if audio_only:
                ytdlp_cmd.extend(['--extract-audio', '--audio-format', 'mp3', '--audio-quality', '0'])
            
            # Add the effective URL
            ytdlp_cmd.append(effective_url)
            
            # Execute yt-dlp command
            self.logger.info(f"Running command: {' '.join(ytdlp_cmd)}")
            result = subprocess.run(ytdlp_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
            
            # Check if download was successful
            if result.returncode == 0 and os.path.exists(output_file) and os.path.getsize(output_file) > 0:
                # Try to get the title from yt-dlp output
                title_match = re.search(r'Destination:\s+(.+?)\s+', result.stdout)
                title = title_match.group(1) if title_match else f"{platform} video {unique_id}"
                
                return {
                    'success': True,
                    'file_path': output_file,
                    'title': title,
                    'format': ext
                }
            else:
                self.logger.error(f"yt-dlp failed: {result.stderr}")
                return {'success': False, 'error': f"Failed to download with yt-dlp: {result.stderr}"}
            
        except Exception as e:
            self.logger.error(f"Error in download_with_curl_ytdlp: {str(e)}")
            return {'success': False, 'error': str(e)}
    
    def download_with_curl_api(self, url, audio_only=False):
        """Download video using curl to access third-party APIs"""
        try:
            self.logger.info(f"Attempting to download {url} with curl + API")
            
            # Generate a unique output filename
            platform = self.get_platform(url)
            unique_id = uuid.uuid4().hex[:8]
            ext = 'mp3' if audio_only else 'mp4'
            output_file = os.path.join(self.download_path, f"{platform}_{unique_id}.{ext}")
            
            # Try Y2mate API
            try:
                # Step 1: Get video info
                analyze_cmd = [
                    'curl', '-s',
                    'https://www.y2mate.com/mates/analyze/ajax',
                    '-d', f"url={url}&q_auto=0&ajax=1",
                    '-H', f"User-Agent: {self.get_random_user_agent()}",
                    '-H', "Content-Type: application/x-www-form-urlencoded",
                    '-H', "Accept: application/json"
                ]
                
                analyze_result = subprocess.check_output(analyze_cmd, stderr=subprocess.PIPE).decode('utf-8')
                analyze_data = json.loads(analyze_result)
                
                if 'result' in analyze_data:
                    # Extract k parameter and title using regex
                    k_match = re.search(r'k=([^"&]+)', analyze_data['result'])
                    title_match = re.search(r'<b>([^<]+)</b>', analyze_data['result'])
                    
                    if k_match:
                        k = k_match.group(1)
                        title = title_match.group(1) if title_match else f"{platform} video {unique_id}"
                        
                        # Step 2: Convert and get download link
                        ftype = 'mp3' if audio_only else 'mp4'
                        fquality = '128' if audio_only else '720p'
                        
                        convert_cmd = [
                            'curl', '-s',
                            'https://www.y2mate.com/mates/convert',
                            '-d', f"type=youtube&_id={k}&v_id={self.get_youtube_video_id(url)}&ajax=1&token=&ftype={ftype}&fquality={fquality}",
                            '-H', f"User-Agent: {self.get_random_user_agent()}",
                            '-H', "Content-Type: application/x-www-form-urlencoded",
                            '-H', "Accept: application/json"
                        ]
                        
                        convert_result = subprocess.check_output(convert_cmd, stderr=subprocess.PIPE).decode('utf-8')
                        convert_data = json.loads(convert_result)
                        
                        if 'result' in convert_data:
                            # Extract download link using regex
                            download_link_match = re.search(r'href="([^"]+)" class="btn btn-success"', convert_data['result'])
                            
                            if download_link_match:
                                download_url = download_link_match.group(1)
                                
                                # Step 3: Download the file
                                download_cmd = [
                                    'curl', '-s', '-L',
                                    '-o', output_file,
                                    '-H', f"User-Agent: {self.get_random_user_agent()}",
                                    download_url
                                ]
                                
                                subprocess.run(download_cmd, check=True)
                                
                                # 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
                                    }
            except Exception as e:
                self.logger.warning(f"Y2mate API failed: {str(e)}")
            
            # Try SaveFrom API
            try:
                # Prepare API request
                api_url = "https://api.savefrom.net/api/convert"
                data = json.dumps({"url": url, "extension": ext})
                
                api_cmd = [
                    'curl', '-s',
                    api_url,
                    '-H', f"User-Agent: {self.get_random_user_agent()}",
                    '-H', "Content-Type: application/json",
                    '-H', "Accept: application/json",
                    '-d', data
                ]
                
                api_result = subprocess.check_output(api_cmd, stderr=subprocess.PIPE).decode('utf-8')
                api_data = json.loads(api_result)
                
                if 'url' in api_data and api_data['url']:
                    download_url = api_data['url']
                    title = api_data.get('title', f"{platform} video {unique_id}")
                    
                    # Download the file
                    download_cmd = [
                        'curl', '-s', '-L',
                        '-o', output_file,
                        '-H', f"User-Agent: {self.get_random_user_agent()}",
                        download_url
                    ]
                    
                    subprocess.run(download_cmd, check=True)
                    
                    # 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
                        }
            except Exception as e:
                self.logger.warning(f"SaveFrom API failed: {str(e)}")
            
            # If all APIs failed, return error
            return {'success': False, 'error': "All API methods failed"}
            
        except Exception as e:
            self.logger.error(f"Error in download_with_curl_api: {str(e)}")
            return {'success': False, 'error': str(e)}
    
    def download_with_youtube_dl(self, url, audio_only=False):
        """Download video using youtube-dl (older version that might still work)"""
        try:
            self.logger.info(f"Attempting to download {url} with youtube-dl")
            
            # Generate a unique output filename
            platform = self.get_platform(url)
            unique_id = uuid.uuid4().hex[:8]
            ext = 'mp3' if audio_only else 'mp4'
            output_template = os.path.join(self.download_path, f"{platform}_{unique_id}.%(ext)s")
            
            # Prepare youtube-dl options
            format_option = 'bestaudio' if audio_only else 'best'
            
            # Prepare youtube-dl command
            ytdl_cmd = [
                'youtube-dl',
                '--no-check-certificate',
                '--geo-bypass',
                '--no-warnings',
                '--ignore-errors',
                '--no-playlist',
                '-f', format_option,
                '-o', output_template
            ]
            
            # Add post-processing for audio if needed
            if audio_only:
                ytdl_cmd.extend(['--extract-audio', '--audio-format', 'mp3', '--audio-quality', '0'])
            
            # Add the URL
            ytdl_cmd.append(url)
            
            # Execute youtube-dl command
            self.logger.info(f"Running command: {' '.join(ytdl_cmd)}")
            result = subprocess.run(ytdl_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
            
            # Check if download was successful
            output_file = os.path.join(self.download_path, f"{platform}_{unique_id}.{ext}")
            if not os.path.exists(output_file):
                # Try to find the actual file
                possible_files = [f for f in os.listdir(self.download_path) if f.startswith(f"{platform}_{unique_id}")]
                if possible_files:
                    output_file = os.path.join(self.download_path, possible_files[0])
            
            if result.returncode == 0 and os.path.exists(output_file) and os.path.getsize(output_file) > 0:
                # Try to get the title from youtube-dl output
                title_match = re.search(r'Destination:\s+(.+?)\s+', result.stdout)
                title = title_match.group(1) if title_match else f"{platform} video {unique_id}"
                
                return {
                    'success': True,
                    'file_path': output_file,
                    'title': title,
                    'format': os.path.splitext(output_file)[1][1:]  # Get extension without dot
                }
            else:
                self.logger.error(f"youtube-dl failed: {result.stderr}")
                return {'success': False, 'error': f"Failed to download with youtube-dl: {result.stderr}"}
            
        except Exception as e:
            self.logger.error(f"Error in download_with_youtube_dl: {str(e)}")
            return {'success': False, 'error': str(e)}
    
    def download_with_direct_command(self, url, audio_only=False):
        """Download video using direct command-line tools without dependencies"""
        try:
            self.logger.info(f"Attempting to download {url} with direct command")
            
            # Generate a unique output filename
            platform = self.get_platform(url)
            unique_id = uuid.uuid4().hex[:8]
            ext = 'mp3' if audio_only else 'mp4'
            output_file = os.path.join(self.download_path, f"{platform}_{unique_id}.{ext}")
            
            # Try multiple commands in sequence
            commands = []
            
            # 1. Try yt-dlp with all options
            commands.append([
                'yt-dlp',
                '--no-check-certificate',
                '--force-generic-extractor',
                '--geo-bypass',
                '--no-warnings',
                '--ignore-errors',
                '--no-playlist',
                '-f', 'bestaudio' if audio_only else 'best',
                '-o', output_file,
                url
            ])
            
            # 2. Try youtube-dl
            commands.append([
                'youtube-dl',
                '--no-check-certificate',
                '--geo-bypass',
                '--no-warnings',
                '--ignore-errors',
                '--no-playlist',
                '-f', 'bestaudio' if audio_only else 'best',
                '-o', output_file,
                url
            ])
            
            # 3. Try wget with user agent
            commands.append([
                'wget',
                '--no-check-certificate',
                '--user-agent', self.get_random_user_agent(),
                '-O', output_file,
                url
            ])
            
            # 4. Try curl
            commands.append([
                'curl', '-L',
                '-A', self.get_random_user_agent(),
                '-o', output_file,
                url
            ])
            
            # Try each command until one succeeds
            for cmd in commands:
                try:
                    self.logger.info(f"Running command: {' '.join(cmd)}")
                    result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=60)
                    
                    # Check if download was successful
                    if result.returncode == 0 and os.path.exists(output_file) and os.path.getsize(output_file) > 0:
                        return {
                            'success': True,
                            'file_path': output_file,
                            'title': f"{platform} video {unique_id}",
                            'format': ext
                        }
                except Exception as e:
                    self.logger.warning(f"Command failed: {str(e)}")
            
            # If all commands failed, return error
            return {'success': False, 'error': "All direct commands failed"}
            
        except Exception as e:
            self.logger.error(f"Error in download_with_direct_command: {str(e)}")
            return {'success': False, 'error': str(e)}
    
    def download(self, url, audio_only=False, compress=True, target_size_mb=10):
        """Main download method with multiple fallback options"""
        self.logger.info(f"Starting download for {url}, audio_only={audio_only}, compress={compress}")
        
        # Try all methods in sequence until one succeeds
        methods = [
            ('curl_ytdlp', self.download_with_curl_ytdlp),
            ('curl_api', self.download_with_curl_api),
            ('youtube_dl', self.download_with_youtube_dl),
            ('direct_command', self.download_with_direct_command)
        ]
        
        for method_name, method_func in methods:
            try:
                self.logger.info(f"Attempting download with method: {method_name}")
                result = method_func(url, audio_only)
                
                if result['success']:
                    self.logger.info(f"Successfully downloaded with method: {method_name}")
                    
                    # If download was successful and it's a video (not audio) and compression is enabled
                    if 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
            except Exception as e:
                self.logger.warning(f"Method {method_name} failed: {str(e)}")
        
        # If all methods failed, return error
        return {'success': False, 'error': "All download methods failed"}

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