"""
URL IO Module

Utilities for reading files over HTTP/HTTPS requests

License: BSD 3-Clause
"""

import os
from pathlib import Path
from typing import Dict, Optional, Any, Union
from urllib.parse import urlparse

try:
    import requests
    REQUESTS_AVAILABLE = True
except ImportError:
    REQUESTS_AVAILABLE = False

from cocina.printer import Printer
from soundhub_utils.utils import flac
from soundhub_utils.io import _shared

# Setup logging
printer = Printer()


def extract_flac_header(src: str) -> Dict:
    """
    Extract FLAC header metadata from an HTTP/HTTPS URL.
    
    Downloads only the first few KB of the file using HTTP Range requests
    to extract header information efficiently.
    
    Args:
        src: HTTP/HTTPS URL to the FLAC file
        
    Returns:
        Dictionary containing FLAC metadata:
        - sample_rate: Sample rate in Hz
        - channels: Number of audio channels
        - bits_per_sample: Bits per sample
        - total_samples: Total number of samples
        - duration: Duration in seconds
        
    Raises:
        ImportError: If requests is not installed
        ValueError: If URL is invalid or file is not a FLAC file
        Exception: Network or HTTP errors
    """
    if not REQUESTS_AVAILABLE:
        raise ImportError("requests is required for URL functionality. Install with: pip install requests")
    
    # Validate URL
    parsed = urlparse(src)
    if parsed.scheme not in ['http', 'https']:
        raise ValueError(f"Invalid URL scheme: {parsed.scheme}. Must be http or https")
    
    try:
        # Download first 4KB using Range request
        headers = {'Range': 'bytes=0-4095'}
        response = requests.get(src, headers=headers, timeout=30, verify=True)
        response.raise_for_status()
        
        header_data = response.content
        
        # Use flac utility to parse header
        return flac.extract_header(header_data)
        
    except requests.RequestException as e:
        printer.error(e, f"Failed to extract FLAC header from {src}")
        raise
    except Exception as e:
        printer.error(e, f"Failed to parse FLAC header from {src}")
        raise


def read(
    src: str,
    dest: Optional[str] = None,
    start_byte: Optional[int] = None,
    end_byte: Optional[int] = None,
    create_parents: bool = True,
    overwrite: bool = True,
    client: Optional[Any] = None
) -> Union[str, Any]:
    """
    Read a file from an HTTP/HTTPS URL.
    
    Args:
        src: HTTP/HTTPS URL to the file
        dest: Optional local destination path. If None, returns response object
        start_byte: Optional start byte for partial reads (HTTP Range header)
        end_byte: Optional end byte for partial reads (HTTP Range header)
        create_parents: Create parent directories if they don't exist
        overwrite: Overwrite destination file if it exists
        client: Not used for URL requests (kept for signature compatibility)
        
    Returns:
        If dest is provided: path to the saved file (str)
        If dest is None: response object from HTTP request
        
    Raises:
        ImportError: If requests is not installed
        ValueError: If URL is invalid
        Exception: Network or HTTP errors
    """
    if not REQUESTS_AVAILABLE:
        raise ImportError("requests is required for URL functionality. Install with: pip install requests")
    
    # Validate URL
    parsed = urlparse(src)
    if parsed.scheme not in ['http', 'https']:
        raise ValueError(f"Invalid URL scheme: {parsed.scheme}. Must be http or https")
    
    # Default request parameters
    request_kwargs = {
        'timeout': 30,
        'verify': True,
        'allow_redirects': True,
        'stream': True  # Stream for large files
    }
    
    # Add Range header if byte range specified
    headers = {}
    if start_byte is not None and end_byte is not None:
        headers['Range'] = f'bytes={start_byte}-{end_byte}'
    elif start_byte is not None:
        headers['Range'] = f'bytes={start_byte}-'
    
    if headers:
        request_kwargs['headers'] = headers
    
    try:
        response = requests.get(src, **request_kwargs)
        response.raise_for_status()
        
        if dest is None:
            # Return response object for further processing
            return response
        else:
            # Download to destination file
            dest_path = Path(dest)
            
            # Create parent directories if requested
            if create_parents:
                dest_path.parent.mkdir(parents=True, exist_ok=True)
            
            # Check if file exists and overwrite flag
            if dest_path.exists() and not overwrite:
                raise ValueError(f"Destination file exists and overwrite=False: {dest}")
            
            # Write content to file
            with dest_path.open('wb') as f:
                for chunk in response.iter_content(chunk_size=8192):
                    if chunk:  # filter out keep-alive chunks
                        f.write(chunk)
            
            printer.message(f"Downloaded {src} to {dest}")
            return str(dest_path)
            
    except requests.RequestException as e:
        printer.error(e, f"Failed to read from {src}")
        raise
    except Exception as e:
        printer.error(e, f"Failed to save file from {src}")
        raise


def read_partial_flac(
    src: str,
    dest: str,
    start_time: float = None,
    end_time: Optional[float] = None,
    duration: Optional[float] = None,
    create_parents: bool = True,
    overwrite: bool = True
) -> str:
    """
    Download a specific time range from an HTTP/HTTPS hosted FLAC file.
    
    Uses FLAC header information and HTTP Range requests to download only
    the requested time segment, minimizing data transfer.
    
    Args:
        src: HTTP/HTTPS URL to the FLAC file
        dest: Local destination path for the downloaded chunk
        start_time: Start time in seconds
        end_time: End time in seconds (optional)
        duration: Duration in seconds (optional)
        create_parents: Create parent directories if they don't exist
        overwrite: Overwrite destination file if it exists
        
    Returns:
        Path to the downloaded FLAC file
        
    Raises:
        ImportError: If requests is not installed
        ValueError: If URL is invalid or time parameters are out of range
        Exception: Network or HTTP errors
    """
    if not REQUESTS_AVAILABLE:
        raise ImportError("requests is required for URL functionality. Install with: pip install requests")
    
    printer.message(f"Reading FLAC partial: {src} -> {dest}, time range: {start_time}-{end_time or start_time + duration if duration else 'end'}s")

    # Get file info for strategy decision
    file_size = _get_file_size(src)
    file_size_mb = file_size / (1024 * 1024)

    # Get FLAC header info
    header_info = extract_flac_header(src)
    total_duration = header_info['duration']

    printer.message(f"File info: duration={total_duration}s, size={file_size_mb:.1f}MB")

    # Validate and calculate time range
    start_time, end_time, duration = _shared.validate_and_calculate_time_range(
        start_time, end_time, duration, total_duration
    )

    # For partial requests, ALWAYS use byte-range downloads (never fall back to full file)
    start_byte, end_byte = flac.estimate_byte_range(file_size, total_duration, start_time, end_time)

    printer.message(f"Using byte-range download strategy: {start_byte}-{end_byte}")
    return _shared.download_and_extract_partial_flac(
        src, dest, start_time, end_time, duration,
        start_byte, end_byte, create_parents, overwrite,
        _download_range
    )


#
# INTERNAL
#
def _get_file_size(url: str, **kwargs) -> int:
    """
    Get the size of a file at a URL using HEAD request.
    
    Args:
        url: HTTP/HTTPS URL
        **kwargs: Additional arguments (headers, auth, timeout, verify)
        
    Returns:
        File size in bytes
        
    Raises:
        Exception: If HEAD request fails or Content-Length not available
    """
    if not REQUESTS_AVAILABLE:
        raise ImportError("requests is required for URL functionality. Install with: pip install requests")
    
    # Default request parameters
    request_kwargs = {
        'timeout': kwargs.get('timeout', 30),
        'verify': kwargs.get('verify', True),
        'allow_redirects': True
    }
    
    # Add optional parameters
    if 'headers' in kwargs:
        request_kwargs['headers'] = kwargs['headers']
    if 'auth' in kwargs:
        request_kwargs['auth'] = kwargs['auth']
    
    try:
        response = requests.head(url, **request_kwargs)
        response.raise_for_status()
        
        content_length = response.headers.get('content-length')
        if content_length is None:
            raise ValueError(f"Content-Length header not available for {url}")
        
        return int(content_length)
        
    except requests.RequestException as e:
        printer.error(e, f"Failed to get file size for {url}")
        raise


def _download_range(url: str, start_byte: int, end_byte: int) -> bytes:
    """Download a byte range from URL using HTTP Range header."""
    if not REQUESTS_AVAILABLE:
        raise ImportError("requests is required for URL functionality. Install with: pip install requests")

    try:
        headers = {'Range': f'bytes={start_byte}-{end_byte}'}
        response = requests.get(url, headers=headers, timeout=30, verify=True)
        response.raise_for_status()
        return response.content
    except requests.RequestException as e:
        printer.error(e, f"Error downloading range {start_byte}-{end_byte} from {url}")
        raise
