"""
Common download utilities with progress reporting and better error handling
"""
import logging
import time
import urllib.request
import urllib.error
from pathlib import Path
from typing import Optional, Callable
from concurrent.futures import ThreadPoolExecutor, as_completed

LOG = logging.getLogger(__name__)


class DownloadError(Exception):
    """Custom exception for download errors."""
    pass


def download_with_progress(
    url: str,
    output_path: Path,
    max_retries: int = 3,
    timeout: int = 30,
    user_agent: str = "gpathex/1.0.0",
    progress_callback: Optional[Callable] = None
) -> bool:
    """
    Download a file with progress reporting and retries.
    
    Args:
        url: URL to download
        output_path: Output file path
        max_retries: Maximum number of retry attempts
        timeout: Request timeout in seconds
        user_agent: User-Agent header
        progress_callback: Optional callback for progress updates
    
    Returns:
        True if successful, False otherwise
    """
    last_error = None
    
    for attempt in range(max_retries):
        try:
            if attempt > 0:
                delay = 2 ** attempt
                LOG.debug(f"Retry {attempt + 1}/{max_retries}, waiting {delay}s...")
                time.sleep(delay)
            
            req = urllib.request.Request(
                url,
                headers={
                    'User-Agent': user_agent,
                    'Accept': '*/*',
                    'Accept-Encoding': 'gzip, deflate',
                }
            )
            
            with urllib.request.urlopen(req, timeout=timeout) as response:
                # Get file size if available
                file_size = int(response.headers.get('Content-Length', 0))
                
                # Create parent directory if needed
                output_path.parent.mkdir(parents=True, exist_ok=True)
                
                downloaded = 0
                chunk_size = 8192  # 8KB chunks
                
                with open(output_path, 'wb') as f:
                    while True:
                        chunk = response.read(chunk_size)
                        if not chunk:
                            break
                        
                        f.write(chunk)
                        downloaded += len(chunk)
                        
                        # Report progress
                        if progress_callback and file_size > 0:
                            progress = (downloaded / file_size) * 100
                            progress_callback(progress, downloaded, file_size)
                
                LOG.info(f"Downloaded {downloaded:,} bytes to {output_path}")
                return True
                
        except urllib.error.HTTPError as e:
            last_error = e
            if e.code == 404:
                LOG.error(f"File not found (404): {url}")
                return False
            elif e.code == 403:
                LOG.error(f"Access forbidden (403): {url}")
                return False
            elif e.code == 429:
                LOG.warning(f"Rate limited (429): {url}. Waiting...")
                time.sleep(10)  # Wait 10 seconds for rate limit
                continue
            else:
                LOG.warning(f"HTTP error {e.code}: {e.reason}")
                
        except urllib.error.URLError as e:
            last_error = e
            LOG.warning(f"URL error: {e.reason}")
            
        except TimeoutError:
            last_error = TimeoutError(f"Timeout after {timeout} seconds")
            LOG.warning(f"Timeout downloading {url}")
            
        except Exception as e:
            last_error = e
            LOG.warning(f"Error downloading {url}: {e}")
    
    # All retries failed
    if last_error:
        LOG.error(f"Failed to download {url} after {max_retries} attempts: {last_error}")
    
    return False


def batch_download(
    url_list: list,
    output_dir: Path,
    concurrent: int = 5,
    **kwargs
) -> dict:
    """
    Download multiple files concurrently.
    
    Args:
        url_list: List of URLs to download
        output_dir: Output directory
        concurrent: Number of concurrent downloads
        **kwargs: Additional arguments for download_with_progress
    
    Returns:
        Dictionary with download results
    """
    results = {
        'successful': [],
        'failed': [],
        'total': len(url_list)
    }
    
    def download_task(url):
        """Task for downloading a single URL."""
        try:
            # Extract filename from URL or generate one
            filename = Path(url).name
            if not filename or filename == Path(url).parent.name:
                # Generate a filename from the last part of URL
                filename = f"download_{hash(url) % 10000:04d}.tmp"
            
            output_path = output_dir / filename
            
            success = download_with_progress(url, output_path, **kwargs)
            
            if success:
                return url, output_path, None
            else:
                return url, None, "Download failed"
                
        except Exception as e:
            return url, None, str(e)
    
    # Execute downloads concurrently
    with ThreadPoolExecutor(max_workers=concurrent) as executor:
        future_to_url = {
            executor.submit(download_task, url): url 
            for url in url_list
        }
        
        for future in as_completed(future_to_url):
            url = future_to_url[future]
            try:
                url, output_path, error = future.result()
                if error:
                    results['failed'].append({'url': url, 'error': error})
                    LOG.error(f"Failed: {url} - {error}")
                else:
                    results['successful'].append({'url': url, 'path': output_path})
                    LOG.info(f"Success: {url} -> {output_path}")
            except Exception as e:
                results['failed'].append({'url': url, 'error': str(e)})
                LOG.error(f"Exception downloading {url}: {e}")
    
    return results


def simple_download(url: str, output_path: Path, **kwargs) -> bool:
    """
    Simple download wrapper without progress reporting.
    
    Args:
        url: URL to download
        output_path: Output file path
        **kwargs: Additional arguments for download_with_progress
    
    Returns:
        True if successful, False otherwise
    """
    return download_with_progress(url, output_path, **kwargs)


__all__ = [
    'DownloadError',
    'download_with_progress',
    'batch_download',
    'simple_download'
]
