"""
Google Cloud Storage IO Module

Utilities for reading and writing files from/to Google Cloud Storage

License: BSD 3-Clause
"""

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

try:
    from google.cloud import storage
    from google.api_core import exceptions as gcs_exceptions
    GCS_AVAILABLE = True
except ImportError:
    GCS_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 a Google Cloud Storage file.
    
    Downloads only the first few KB of the file to extract header information
    efficiently without downloading the entire file.
    
    Args:
        src: Google Cloud Storage URI (gs://bucket/path/file.flac)
        
    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 google-cloud-storage is not installed
        ValueError: If URI is invalid or file is not a FLAC file
        Exception: Network or authentication errors
    """
    if not GCS_AVAILABLE:
        raise ImportError("google-cloud-storage is required for GCS functionality. Install with: pip install google-cloud-storage")
    
    bucket_name, object_name = _parse_gcs_uri(src)
    client = storage.Client()
    bucket = client.bucket(bucket_name)
    blob = bucket.blob(object_name)
    
    try:
        # Download first 4KB to get FLAC metadata
        header_data = blob.download_as_bytes(start=0, end=4095)
        
        # Use flac utility to parse header
        return flac.extract_header(header_data)
        
    except gcs_exceptions.NotFound:
        raise ValueError(f"File not found: {src}")
    except gcs_exceptions.Forbidden:
        raise ValueError(f"Access denied: {src}")
    except Exception as e:
        printer.error(e, f"Failed to extract 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 Google Cloud Storage.
    
    Args:
        src: Google Cloud Storage URI (gs://bucket/path/file)
        dest: Optional local destination path. If None, returns blob object
        start_byte: Optional start byte for partial reads
        end_byte: Optional end byte for partial reads  
        create_parents: Create parent directories if they don't exist
        overwrite: Overwrite destination file if it exists
        client: Optional GCS client (if None, creates one)
        
    Returns:
        If dest is provided: path to the saved file (str)
        If dest is None: blob object from GCS
        
    Raises:
        ImportError: If google-cloud-storage is not installed
        ValueError: If URI is invalid
        Exception: Network or authentication errors
    """
    if not GCS_AVAILABLE:
        raise ImportError("google-cloud-storage is required for GCS functionality. Install with: pip install google-cloud-storage")
    
    bucket_name, object_name = _parse_gcs_uri(src)
    
    # Use provided client or create new one
    if client is None:
        client = storage.Client()
    
    bucket = client.bucket(bucket_name)
    blob = bucket.blob(object_name)
    
    try:
        if dest is None:
            # Return blob object for further processing
            return blob
        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}")
            
            # Download file with optional byte range
            if start_byte is not None and end_byte is not None:
                data = blob.download_as_bytes(start=start_byte, end=end_byte)
                dest_path.write_bytes(data)
            elif start_byte is not None:
                # Download from start_byte to end
                data = blob.download_as_bytes(start=start_byte)
                dest_path.write_bytes(data)
            else:
                # Download entire file
                blob.download_to_filename(str(dest_path))
            
            printer.message(f"Downloaded {src} to {dest}")
            return str(dest_path)
            
    except gcs_exceptions.NotFound:
        raise ValueError(f"File not found: {src}")
    except gcs_exceptions.Forbidden:
        raise ValueError(f"Access denied: {src}")
    except Exception as e:
        printer.error(e, f"Failed to read 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 a Google Cloud Storage FLAC file.
    
    Uses FLAC header information and byte-range requests to download only
    the requested time segment, minimizing data transfer.
    
    Args:
        src: Google Cloud Storage URI (gs://bucket/path/file.flac)
        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 google-cloud-storage is not installed
        ValueError: If URI is invalid or time parameters are out of range
        Exception: Network or authentication errors
    """
    if not GCS_AVAILABLE:
        raise ImportError("google-cloud-storage is required for GCS functionality. Install with: pip install google-cloud-storage")
    
    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 _parse_gcs_uri(gcs_uri: str) -> Tuple[str, str]:
    """
    Parse GCS URI into bucket and object name components.
    
    Args:
        gcs_uri: GCS URI (may or may not include gs:// prefix)
        
    Returns:
        Tuple of (bucket_name, object_name)
        
    Raises:
        ValueError: If URI format is invalid
    """
    if not gcs_uri.startswith('gs://'):
        gcs_uri = 'gs://' + gcs_uri
    
    parsed = urlparse(gcs_uri)
    if parsed.scheme != 'gs' or not parsed.netloc or not parsed.path:
        raise ValueError(f"Invalid GCS URI format: {gcs_uri}")
    
    bucket_name = parsed.netloc
    object_name = parsed.path.lstrip('/')
    
    if not object_name:
        raise ValueError(f"No object name specified in GCS URI: {gcs_uri}")
    
    return bucket_name, object_name


def _get_file_size(gcs_uri: str) -> int:
    """
    Get the size of a GCS object in bytes.
    
    Args:
        gcs_uri: GCS URI
        
    Returns:
        File size in bytes
        
    Raises:
        Exception: If file doesn't exist or access denied
    """
    if not GCS_AVAILABLE:
        raise ImportError("google-cloud-storage is required for GCS functionality. Install with: pip install google-cloud-storage")
    
    bucket_name, object_name = _parse_gcs_uri(gcs_uri)
    client = storage.Client()
    bucket = client.bucket(bucket_name)
    blob = bucket.blob(object_name)
    
    # Reload to get current metadata
    blob.reload()
    
    if blob.size is None:
        raise ValueError(f"Could not determine file size for {gcs_uri}")
    
    return blob.size


def _download_range(gcs_uri: str, start_byte: int, end_byte: int) -> bytes:
    """Download a byte range from GCS."""
    if not GCS_AVAILABLE:
        raise ImportError("google-cloud-storage is required for GCS functionality. Install with: pip install google-cloud-storage")

    bucket_name, object_name = _parse_gcs_uri(gcs_uri)
    client = storage.Client()
    bucket = client.bucket(bucket_name)
    blob = bucket.blob(object_name)

    try:
        # Download byte range
        data = blob.download_as_bytes(start=start_byte, end=end_byte)
        return data
    except gcs_exceptions.NotFound:
        raise ValueError(f"File not found: {gcs_uri}")
    except gcs_exceptions.Forbidden:
        raise ValueError(f"Access denied: {gcs_uri}")
    except Exception as e:
        printer.error(e, f"Error downloading range {start_byte}-{end_byte} from {gcs_uri}")
        raise
