"""
JavaScript Download Module - With caching and parallel processing
"""

import asyncio
import aiohttp
import requests
from pathlib import Path
from typing import List, Dict
from urllib.parse import urlparse
import hashlib
from concurrent.futures import ThreadPoolExecutor

from ..utils.logger import log_progress
from ..utils.fs import ensure_dir
from ..utils.cache import JSEyeCache

class JSDownloader:
    """Download JavaScript files with caching and parallel processing"""
    
    def __init__(self, output_dir: Path):
        self.output_dir = output_dir
        self.js_dir = output_dir / "js_files"
        ensure_dir(self.js_dir)
        
        # Initialize cache
        self.cache = JSEyeCache(output_dir)
        
        # Download settings
        self.max_file_size = 3 * 1024 * 1024  # 3MB limit
        self.timeout = 30  # 30 seconds timeout
        self.max_concurrent = 10  # Max concurrent downloads
        
        self.session_headers = {
            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
        }
    
    async def download_js_file_async(self, session: aiohttp.ClientSession, url: str) -> Dict:
        """Download a single JavaScript file asynchronously with caching"""
        
        # Check cache first
        cached_result = self.cache.get_download_cache(url)
        if cached_result:
            return cached_result
        
        try:
            async with session.get(url, timeout=aiohttp.ClientTimeout(total=self.timeout)) as response:
                # Check content length
                content_length = response.headers.get('content-length')
                if content_length and int(content_length) > self.max_file_size:
                    result = {
                        'url': url,
                        'filepath': None,
                        'size': int(content_length),
                        'status': 'too_large',
                        'error': f'File too large: {int(content_length)} bytes'
                    }
                    self.cache.set_download_cache(url, result)
                    return result
                
                # Read content with size limit
                content = await response.read()
                
                if len(content) > self.max_file_size:
                    result = {
                        'url': url,
                        'filepath': None,
                        'size': len(content),
                        'status': 'too_large',
                        'error': f'Content too large: {len(content)} bytes'
                    }
                    self.cache.set_download_cache(url, result)
                    return result
                
                # Generate filename from URL hash
                url_hash = hashlib.md5(url.encode()).hexdigest()[:8]
                parsed = urlparse(url)
                domain = parsed.netloc.replace('.', '_')
                filename = f"{domain}_{url_hash}.js"
                
                filepath = self.js_dir / filename
                
                # Write file
                with open(filepath, 'wb') as f:
                    f.write(content)
                
                result = {
                    'url': url,
                    'filepath': str(filepath),
                    'size': len(content),
                    'status': 'success'
                }
                
                # Cache the result
                self.cache.set_download_cache(url, result)
                
                return result
                
        except asyncio.TimeoutError:
            result = {
                'url': url,
                'filepath': None,
                'size': 0,
                'status': 'timeout',
                'error': 'Download timeout'
            }
            self.cache.set_download_cache(url, result)
            return result
            
        except Exception as e:
            result = {
                'url': url,
                'filepath': None,
                'size': 0,
                'status': 'failed',
                'error': str(e)
            }
            self.cache.set_download_cache(url, result)
            return result
    
    async def download_js_files_parallel(self, urls: List[str]) -> List[Dict]:
        """Download multiple JavaScript files in parallel"""
        log_progress(f">> Downloading {len(urls)} JavaScript files in parallel...")
        
        # Create semaphore to limit concurrent downloads
        semaphore = asyncio.Semaphore(self.max_concurrent)
        
        async def download_with_semaphore(session, url):
            async with semaphore:
                return await self.download_js_file_async(session, url)
        
        # Create aiohttp session
        connector = aiohttp.TCPConnector(limit=self.max_concurrent)
        timeout = aiohttp.ClientTimeout(total=self.timeout)
        
        async with aiohttp.ClientSession(
            connector=connector,
            timeout=timeout,
            headers=self.session_headers
        ) as session:
            
            # Create tasks for all downloads
            tasks = [download_with_semaphore(session, url) for url in urls]
            
            # Execute all downloads in parallel
            results = await asyncio.gather(*tasks, return_exceptions=True)
            
            # Filter out exceptions
            valid_results = []
            for i, result in enumerate(results):
                if isinstance(result, Exception):
                    log_progress(f"Download exception for {urls[i]}: {result}")
                    valid_results.append({
                        'url': urls[i],
                        'filepath': None,
                        'size': 0,
                        'status': 'exception',
                        'error': str(result)
                    })
                else:
                    valid_results.append(result)
        
        # Count successful downloads
        successful = len([r for r in valid_results if r['status'] == 'success'])
        cached = len([r for r in valid_results if 'cached_at' in r])
        
        log_progress(f"[C] Download complete: {successful} new, {cached} cached, {len(valid_results)} total")
        
        return valid_results
    
    def download_js_files(self, urls: List[str], max_files: int = 200) -> List[Dict]:
        """
        Download JavaScript files (main entry point)
        
        Args:
            urls: List of JavaScript URLs
            max_files: Maximum number of files to download
            
        Returns:
            List of download results
        """
        # Limit number of files
        urls_to_download = urls[:max_files]
        
        try:
            # Use async download
            results = asyncio.run(self.download_js_files_parallel(urls_to_download))
        except Exception as e:
            log_progress(f"Parallel download failed, falling back to sequential: {e}")
            results = self.download_js_files_sequential(urls_to_download)
        
        return results
    
    def download_js_files_sequential(self, urls: List[str]) -> List[Dict]:
        """Fallback sequential download"""
        log_progress("Using sequential download (fallback)")
        
        results = []
        session = requests.Session()
        session.headers.update(self.session_headers)
        
        for i, url in enumerate(urls, 1):
            if i % 20 == 0:
                log_progress(f"Downloaded {i}/{len(urls)} files")
            
            # Check cache first
            cached_result = self.cache.get_download_cache(url)
            if cached_result:
                results.append(cached_result)
                continue
            
            try:
                response = session.get(url, timeout=self.timeout, stream=True)
                
                # Check content length
                content_length = response.headers.get('content-length')
                if content_length and int(content_length) > self.max_file_size:
                    result = {
                        'url': url,
                        'filepath': None,
                        'size': int(content_length),
                        'status': 'too_large'
                    }
                    results.append(result)
                    continue
                
                # Read content with size limit
                content = b''
                for chunk in response.iter_content(chunk_size=8192):
                    content += chunk
                    if len(content) > self.max_file_size:
                        break
                
                if len(content) > self.max_file_size:
                    result = {
                        'url': url,
                        'filepath': None,
                        'size': len(content),
                        'status': 'too_large'
                    }
                    results.append(result)
                    continue
                
                # Generate filename and save
                url_hash = hashlib.md5(url.encode()).hexdigest()[:8]
                parsed = urlparse(url)
                domain = parsed.netloc.replace('.', '_')
                filename = f"{domain}_{url_hash}.js"
                filepath = self.js_dir / filename
                
                with open(filepath, 'wb') as f:
                    f.write(content)
                
                result = {
                    'url': url,
                    'filepath': str(filepath),
                    'size': len(content),
                    'status': 'success'
                }
                
                # Cache result
                self.cache.set_download_cache(url, result)
                results.append(result)
                
            except Exception as e:
                result = {
                    'url': url,
                    'filepath': None,
                    'size': 0,
                    'status': 'failed',
                    'error': str(e)
                }
                results.append(result)
        
        successful = len([r for r in results if r['status'] == 'success'])
        log_progress(f"Sequential download complete: {successful}/{len(urls)} successful")
        
        return results