"""
FLAC Utility Functions

Utilities specific to FLAC file format handling and processing

License: BSD 3-Clause
"""

import struct
from typing import Dict
from pathlib import Path
import tempfile
import os

from pydub import AudioSegment
from cocina.printer import Printer
printer = Printer()


def extract_header(header_data: bytes) -> Dict:
    """
    Extract FLAC header metadata from header bytes.
    
    Args:
        header_data: First ~4KB of FLAC file data
        
    Returns:
        Dict containing duration, sample_rate, channels, total_samples
    """
    # Parse FLAC header
    if header_data[:4] != b'fLaC':
        raise ValueError("Not a valid FLAC file")
    
    # Find STREAMINFO block (should be first metadata block)
    pos = 4
    while pos < len(header_data) - 34:
        block_type = header_data[pos] & 0x7f
        is_last = (header_data[pos] & 0x80) != 0
        
        # Get block length (24-bit big endian)
        length = struct.unpack('>I', b'\x00' + header_data[pos+1:pos+4])[0]
        
        if block_type == 0:  # STREAMINFO block
            streaminfo = header_data[pos+4:pos+4+length]
            
            # Parse STREAMINFO (34 bytes)
            sample_rate = struct.unpack('>I', b'\x00' + streaminfo[10:13])[0] >> 4
            channels = ((struct.unpack('>H', streaminfo[12:14])[0] >> 1) & 0x07) + 1
            bits_per_sample = ((struct.unpack('>H', streaminfo[12:14])[0] << 4) & 0x1f0) >> 4
            bits_per_sample += ((streaminfo[14] >> 4) & 0x01) + 1
            total_samples = struct.unpack('>Q', b'\x00\x00\x00' + streaminfo[13:18])[0] & 0xfffffffff
            
            duration = total_samples / sample_rate if sample_rate > 0 else 0
            
            return {
                'duration': duration,
                'sample_rate': sample_rate,
                'channels': channels,
                'bits_per_sample': bits_per_sample,
                'total_samples': total_samples
            }
        
        pos += 4 + length
        if is_last:
            break
            
    raise ValueError("No STREAMINFO block found in FLAC header")


def should_use_range_method(file_size: int, total_duration: float, requested_duration: float) -> bool:
    """
    Determine whether to use range download method based on file size and chunk ratio.
    
    Args:
        file_size: File size in bytes
        total_duration: Total audio duration in seconds
        requested_duration: Requested chunk duration in seconds
        
    Returns:
        True if should use range method, False to download full file
    """
    file_size_mb = file_size / (1024 * 1024)
    chunk_ratio = requested_duration / total_duration if total_duration > 0 else 1.0
    
    # Use range method for large files when requesting small chunks
    use_range = (file_size_mb > 50) and (chunk_ratio < 0.33)
    
    printer.message(f"File size: {file_size_mb:.1f}MB, Chunk ratio: {chunk_ratio:.2f}, Using range method: {use_range}")
    return use_range


def estimate_byte_range(file_size: int, total_duration: float, start_time: float, 
                       end_time: float, padding_ratio: float = 0.25) -> tuple[int, int]:
    """
    Estimate byte range for audio segment with padding.
    
    Args:
        file_size: Total file size in bytes
        total_duration: Total audio duration in seconds
        start_time: Start time in seconds
        end_time: End time in seconds  
        padding_ratio: Padding ratio (0.25 = 25% padding on each side)
        
    Returns:
        Tuple of (start_byte, end_byte)
    """
    duration = end_time - start_time
    padding_time = duration * padding_ratio
    
    # Calculate padded time range
    padded_start = max(0, start_time - padding_time)
    padded_end = min(total_duration, end_time + padding_time)
    
    # Estimate bytes per second (conservative)
    bytes_per_second = file_size / total_duration
    
    # Calculate byte range
    start_byte = int(padded_start * bytes_per_second)
    end_byte = min(file_size - 1, int(padded_end * bytes_per_second))
    
    printer.message(f"Time range: {start_time:.1f}-{end_time:.1f}s, "
                f"Padded: {padded_start:.1f}-{padded_end:.1f}s, "
                f"Byte range: {start_byte}-{end_byte}")
    
    return start_byte, end_byte


def extract_audio_segment(audio_data: bytes, start_time: float, end_time: float, 
                         use_range: bool = False, padded_start: float = 0.0) -> AudioSegment:
    """
    Extract audio segment from FLAC data using pydub.
    
    Args:
        audio_data: FLAC audio data bytes
        start_time: Start time in seconds
        end_time: End time in seconds
        use_range: Whether this is from a range download
        padded_start: Start time of the padded range (for range downloads)
        
    Returns:
        AudioSegment with the extracted audio
    """
    requested_duration = end_time - start_time
    
    with tempfile.NamedTemporaryFile(suffix='.flac', delete=False) as temp_file:
        temp_path = temp_file.name
        temp_file.write(audio_data)
        temp_file.flush()
        
        try:
            # Load audio and extract the requested segment
            printer.message(f"Loading audio from temporary file: {temp_path}")
            audio = AudioSegment.from_file(temp_path, format="flac")
            
            # Extract the specific time segment
            if use_range:
                # For range downloads, we need to be more careful about the timing
                relative_start = start_time - padded_start
                relative_start_ms = int(relative_start * 1000)
                duration_ms = int(requested_duration * 1000)
                chunk = audio[relative_start_ms:relative_start_ms + duration_ms]
            else:
                # For full downloads, use the absolute times
                start_ms = int(start_time * 1000)
                end_ms = int(end_time * 1000)
                chunk = audio[start_ms:end_ms]
            
            # Validate the result
            actual_duration = len(chunk) / 1000.0
            printer.message(f"Extracted audio: requested={requested_duration:.2f}s, actual={actual_duration:.2f}s")
            
            if abs(actual_duration - requested_duration) > 1.0:  # Allow 1s tolerance
                printer.message(f"Duration mismatch: requested {requested_duration:.2f}s, got {actual_duration:.2f}s")
            
            return chunk
            
        finally:
            # Clean up temporary file
            if os.path.exists(temp_path):
                os.unlink(temp_path)


def extract_time_range(
    src_path: str,
    dest: str, 
    start_time: float,
    end_time: float = None,
    duration: float = None,
    create_parents: bool = True,
    overwrite: bool = True
) -> str:
    """
    Extract a time range from a FLAC file and save to destination.
    
    Args:
        src_path: Path to source FLAC file
        dest: Destination file path
        start_time: Start time in seconds
        end_time: End time in seconds (if None, use start_time + duration)
        duration: Duration in seconds (ignored if end_time is provided)
        create_parents: Create parent directories if needed
        overwrite: Overwrite existing destination file
        
    Returns:
        Path to the saved file (dest)
    """
    # Validate inputs
    if end_time is None:
        if duration is None:
            raise ValueError("Either end_time or duration must be provided")
        end_time = start_time + duration
    
    if start_time < 0:
        raise ValueError("start_time must be >= 0")
    
    if end_time <= start_time:
        raise ValueError("end_time must be > start_time")
    
    # Setup destination path
    dest_path = Path(dest)
    if create_parents:
        dest_path.parent.mkdir(parents=True, exist_ok=True)
    
    if dest_path.exists() and not overwrite:
        raise FileExistsError(f"Destination file {dest} already exists and overwrite=False")
    
    printer.message(f"Extracting time range {start_time:.1f}-{end_time:.1f}s from {src_path} to {dest}")
    
    try:
        # Load the source FLAC file
        audio = AudioSegment.from_file(src_path, format="flac")
        
        # Extract the time segment
        start_ms = int(start_time * 1000)
        end_ms = int(end_time * 1000)
        chunk = audio[start_ms:end_ms]
        
        # Export as FLAC
        chunk.export(str(dest_path), format="flac")
        
        # Validate result
        actual_duration = len(chunk) / 1000.0
        requested_duration = end_time - start_time
        printer.message(f"Extracted audio: requested={requested_duration:.2f}s, actual={actual_duration:.2f}s")
        
        if abs(actual_duration - requested_duration) > 1.0:  # Allow 1s tolerance
            printer.message(f"Duration mismatch: requested {requested_duration:.2f}s, got {actual_duration:.2f}s")
        
        return str(dest_path)
        
    except Exception as e:
        printer.error(e, f"Error extracting time range from {src_path}")
        raise