"""
audio_mastering.py - Mastering Chain y QA
T078-T090: Devices, Loudness, QA Suite
T166-T170: LUFS Estimation, Headroom, Presets
"""
import logging
from typing import Dict, Any, List, Optional, Tuple
from dataclasses import dataclass
import math

logger = logging.getLogger("AudioMastering")


LUFS_DEPENDENCIES_AVAILABLE = False
try:
    import numpy as np
    NUMPY_AVAILABLE = True
except ImportError:
    NUMPY_AVAILABLE = False
    np = None

try:
    import pyloudnorm as pyln
    LUFS_DEPENDENCIES_AVAILABLE = True
except ImportError:
    pyln = None
    LUFS_DEPENDENCIES_AVAILABLE = False


@dataclass
class LUFSMeter:
    """Medición de loudness integrado"""
    integrated: float  # LUFS integrado
    short_term: float  # LUFS short-term (3s)
    momentary: float   # LUFS momentary (400ms)
    true_peak: float   # dBTP
    headroom_db: float = 0.0  # T168: Headroom in dB
    peak_db: float = 0.0  # Peak dBFS


class MasterChain:
    """T078-T082: Mastering chain con devices"""

    def __init__(self):
        self.devices = []
        self._setup_default_chain()

    def _setup_default_chain(self):
        """Configura cadena por defecto: Utility → Saturator → Compressor → Limiter"""
        self.devices = [
            {
                'type': 'Utility',
                'params': {'Gain': 0.0, 'Bass Mono': True, 'Width': 1.0},
                'position': 0
            },
            {
                'type': 'Saturator',
                'params': {'Drive': 1.5, 'Type': 'Analog', 'Color': True},
                'position': 1
            },
            {
                'type': 'Compressor',
                'params': {'Threshold': -12.0, 'Ratio': 2.0, 'Attack': 10.0, 'Release': 100.0},
                'position': 2
            },
            {
                'type': 'Limiter',
                'params': {'Ceiling': -0.3, 'Auto-Release': True},
                'position': 3
            }
        ]

    def get_ableton_device_chain(self) -> List[Dict]:
        """Retorna chain en formato compatible con Ableton Live."""
        return sorted(self.devices, key=lambda x: x['position'])

    def set_limiter_ceiling(self, ceiling_db: float):
        """Ajusta ceiling del limiter (T082)."""
        for device in self.devices:
            if device['type'] == 'Limiter':
                device['params']['Ceiling'] = ceiling_db


class LoudnessAnalyzer:
    """T083-T086: Análisis de loudness
    T166: LUFS estimation with headroom analysis
    """

    TARGETS = {
        'streaming': -14.0,  # Spotify, Apple Music
        'club': -8.0,        # Club/DJ
        'master': -10.0,     # Broadcast
        'reggaeton': -7.0,   # T169: Reggaeton optimized
    }

    def __init__(self):
        self.peak_threshold = -1.0  # dBTP
        self.headroom_target = 0.5  # dB minimum headroom (T168)

    def estimate_integrated_lufs(self, audio_data: Any = None, 
                                   estimated_peak_db: float = -0.5,
                                   estimated_rms_db: float = -14.0) -> LUFSMeter:
        """
        T166: Estimate integrated LUFS from audio or simulation.
        
        When pyloudnorm is not available, uses estimated peak/RMS to approximate LUFS.
        
        Args:
            audio_data: Optional audio samples (numpy array or list)
            estimated_peak_db: Peak level in dBFS (used if no audio_data)
            estimated_rms_db: RMS level in dBFS (used if no audio_data)
        
        Returns:
            LUFSMeter with integrated, short-term, momentary, and true peak estimates
        """
        if LUFS_DEPENDENCIES_AVAILABLE and audio_data is not None:
            try:
                return self._analyze_with_pyloudnorm(audio_data)
            except Exception as e:
                logger.warning(f"[T166] pyloudnorm analysis failed: {e}, using estimation")
        
        # T166: Estimation mode when pyloudnorm unavailable or no audio
        # LUFS is typically -18 to -9 dBFS offset from RMS depending on crest factor
        # True peak is often ~0.3 dB above sample peak
        crest_factor_estimate = abs(estimated_peak_db - estimated_rms_db)
        
        # LUFS estimate: RMS - crest_factor/2 (approximation)
        # More dynamic = higher crest = lower LUFS relative to peak
        lufs_offset = crest_factor_estimate * 0.5 + 3.0  # Empirical formula
        integrated_lufs = estimated_rms_db - lufs_offset
        
        # True peak is usually 0.3-0.8 dB above peak for typical program material
        true_peak = estimated_peak_db + 0.5
        
        # Short-term and momentary variations (typical ±1-2 LUFS)
        short_term = integrated_lufs + 1.0
        momentary = integrated_lufs + 2.0
        
        # T168: Calculate headroom
        headroom_db = -estimated_peak_db
        
        return LUFSMeter(
            integrated=round(integrated_lufs, 1),
            short_term=round(short_term, 1),
            momentary=round(momentary, 1),
            true_peak=round(true_peak, 2),
            headroom_db=round(headroom_db, 2),
            peak_db=round(estimated_peak_db, 2)
        )

    def _analyze_with_pyloudnorm(self, audio_data: Any) -> LUFSMeter:
        """Analyze using pyloudnorm library when available."""
        if not LUFS_DEPENDENCIES_AVAILABLE or pyln is None:
            raise ImportError("pyloudnorm not available")
        
        # Assume audio_data is numpy array with shape (samples,) or (samples, channels)
        sample_rate = 44100  # Default sample rate
        
        meter = pyln.Meter(sample_rate)
        integrated_lufs = meter.integrated_loudness(audio_data)
        
        # Calculate true peak (simplified)
        peak = np.max(np.abs(audio_data)) if NUMPY_AVAILABLE and np is not None else 0.5
        true_peak_db = 20 * math.log10(peak) if peak > 0 else -60.0
        true_peak = true_peak_db + 0.5  # Approximate true peak
        
        # Short-term and momentary estimates (approximation)
        short_term = integrated_lufs + 1.0
        momentary = integrated_lufs + 2.0
        
        # Headroom calculation
        headroom_db = -true_peak_db
        
        return LUFSMeter(
            integrated=round(integrated_lufs, 1),
            short_term=round(short_term, 1),
            momentary=round(momentary, 1),
            true_peak=round(true_peak, 2),
            headroom_db=round(headroom_db, 2),
            peak_db=round(true_peak_db, 2)
        )

    def analyze_loudness(self, audio_data: Any) -> LUFSMeter:
        """
        T084-T085: Analiza loudness de audio.
        Retorna medidas LUFS y true peak.
        """
        return self.estimate_integrated_lufs(audio_data)

    def check_true_peak(self, audio_data: Any) -> Tuple[bool, float]:
        """Verifica si hay true peak clipping."""
        meter = self.analyze_loudness(audio_data)
        is_safe = meter.true_peak < self.peak_threshold
        return is_safe, meter.true_peak

    def suggest_gain_adjustment(self, current_lufs: float, target: str = 'streaming') -> float:
        """Sugiere ajuste de ganancia para alcanzar target LUFS."""
        target_lufs = self.TARGETS.get(target, -14.0)
        return target_lufs - current_lufs
    
    def verify_headroom(self, peak_db: float, target_lufs: float = -14.0) -> Dict[str, Any]:
        """
        T168: Verify headroom before mastering.
        
        Args:
            peak_db: Current peak level in dBFS
            target_lufs: Target LUFS for mastering
        
        Returns:
            Dict with headroom status, warnings, and recommendations
        """
        headroom_db = -peak_db  # e.g., peak=-3.0dBFS → headroom=3dB
        min_headroom = self.headroom_target
        recommended_headroom = 3.0  # 3dB for mastering flexibility
        
        result = {
            'headroom_db': headroom_db,
            'peak_db': peak_db,
            'target_lufs': target_lufs,
            'min_headroom': min_headroom,
            'recommended_headroom': recommended_headroom,
            'is_safe': headroom_db >= min_headroom,
            'warnings': [],
            'recommendations': []
        }
        
        if headroom_db < min_headroom:
            result['warnings'].append(f"Insufficient headroom: {headroom_db:.1f}dB < {min_headroom}dB minimum")
            result['warnings'].append(f"Peak at {peak_db:.1f}dBFS leaves no room for mastering")
            result['recommendations'].append(f"Reduce peak by {min_headroom - headroom_db:.1f}dB before mastering")
        
        if headroom_db < recommended_headroom:
            result['recommendations'].append(f"Consider leaving {recommended_headroom}dB headroom for optimal mastering")
        
        if headroom_db > 12.0:
            result['warnings'].append(f"Excessive headroom: {headroom_db:.1f}dB may indicate mix is too quiet")
            result['recommendations'].append("Normalize mix before mastering")
        
        # Check for clipping
        if peak_db >= -0.1:
            result['warnings'].append("Peak is at or near 0dBFS - mix may be clipping")
            result['recommendations'].append("Reduce mix gain by at least 1dB before mastering")
        
        result['gain_adjustment_for_target'] = round(target_lufs - (peak_db - 10), 1)  # Rough estimate
        
        return result


class QASuite:
    """T087-T090: Quality Assurance Suite"""

    def __init__(self):
        self.issues = []
        self.thresholds = {
            'dc_offset': 0.01,      # 1%
            'stereo_width_min': 0.5,
            'stereo_width_max': 1.5,
            'silence_threshold': -60.0,  # dB
        }

    def detect_clipping(self, audio_data: Any) -> List[Dict]:
        """T087: Detección de clipping en master."""
        # Simulación - verificaría samples > 0 dBFS
        return []

    def check_dc_offset(self, audio_data: Any) -> Tuple[bool, float]:
        """T088: Verifica DC offset."""
        # Simulación - mediría offset en señal
        offset = 0.0
        return abs(offset) < self.thresholds['dc_offset'], offset

    def validate_stereo_field(self, audio_data: Any) -> Dict:
        """T089: Validación de campo estéreo."""
        width = 1.0  # Simulación
        return {
            'width': width,
            'valid': self.thresholds['stereo_width_min'] <= width <= self.thresholds['stereo_width_max'],
            'mono_compatible': width > 0.3
        }

    def run_full_qa(self, audio_data: Any, config: Dict) -> Dict:
        """T090: Suite completa de QA."""
        self.issues = []

        # 1. Clipping
        clipping = self.detect_clipping(audio_data)
        if clipping:
            self.issues.append({'severity': 'error', 'type': 'clipping', 'count': len(clipping)})

        # 2. DC Offset
        dc_ok, dc_value = self.check_dc_offset(audio_data)
        if not dc_ok:
            self.issues.append({'severity': 'warning', 'type': 'dc_offset', 'value': dc_value})

        # 3. Stereo
        stereo = self.validate_stereo_field(audio_data)
        if not stereo['valid']:
            self.issues.append({'severity': 'warning', 'type': 'stereo_width', 'value': stereo['width']})

        # 4. Loudness
        analyzer = LoudnessAnalyzer()
        loudness = analyzer.analyze_loudness(audio_data)
        if loudness.true_peak > -1.0:
            self.issues.append({'severity': 'warning', 'type': 'true_peak', 'value': loudness.true_peak})

        return {
            'passed': len([i for i in self.issues if i['severity'] == 'error']) == 0,
            'issues': self.issues,
            'metrics': {
                'lufs_integrated': loudness.integrated,
                'true_peak': loudness.true_peak,
                'stereo_width': stereo['width'],
            }
        }


class MasteringPreset:
    """Presets de mastering para diferentes destinos"""

    @staticmethod
    def get_preset(name: str) -> Dict:
        """Retorna preset de mastering."""
        presets = {
            'club': {
                'target_lufs': -8.0,
                'ceiling': -0.3,
                'saturator_drive': 2.0,
                'compressor_ratio': 4.0,
                'description': 'Club/DJ mastering for loud playback systems'
            },
            'streaming': {
                'target_lufs': -14.0,
                'ceiling': -1.0,
                'saturator_drive': 1.0,
                'compressor_ratio': 2.0,
                'description': 'Streaming platforms (Spotify, Apple Music)'
            },
            'safe': {
                'target_lufs': -12.0,
                'ceiling': -0.5,
                'saturator_drive': 1.5,
                'compressor_ratio': 2.0,
                'description': 'Safe mastering with headroom'
            },
            # T169: Reggaeton club preset - optimized for 95 BPM reggaeton
            'reggaeton_club': {
                'target_lufs': -7.0,  # Loud for club systems
                'ceiling': -0.2,     # Tight ceiling for reggaeton's heavy low-end
                'saturator_drive': 2.5,  # More drive for punch
                'compressor_ratio': 3.5,  # Medium compression
                'compressor_attack': 8.0,  # Fast attack for transients
                'compressor_release': 120.0,  # Medium release
                'bass_mono_freq': 80.0,  # Mono below 80Hz for sub focus
                'stereo_width': 1.1,  # Slightly wider than mono
                'limiter_release': 'auto',  # Auto-release for varying material
                'description': 'Reggaeton 95 BPM club mastering - loud, punchy, mono bass',
                'chain': ['Utility', 'Saturator', 'Compressor', 'EQ Eight', 'Limiter'],
                'genre_specific': {
                    'kick_emphasis': True,
                    'sub_bass_mono': True,
                    'dem_bow_optimized': True  # Reggaeton rhythm optimization
                }
            }
        }
        return presets.get(name, presets['safe'])


class StemExporter:
    """T088: Exportador de stems 24-bit/44.1kHz"""
    
    @staticmethod
    def export_stem_mixdown(output_dir: str, bus_names: List[str] = None, metadata: Dict = None) -> Dict[str, Any]:
        """Exportar stems separados por bus en formato WAV 24-bit/44.1kHz"""
        if bus_names is None:
            bus_names = ['drums', 'bass', 'music', 'vocals', 'fx', 'master']
        
        from datetime import datetime
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        
        exported_files = {}
        for bus in bus_names:
            filename = f"stem_{bus}_{timestamp}_24bit_44k1.wav"
            filepath = f"{output_dir}/{filename}"
            
            exported_files[bus] = {
                'path': filepath,
                'filename': filename,
                'bus': bus,
                'format': 'WAV',
                'bit_depth': 24,
                'sample_rate': 44100,
                'metadata': metadata or {}
            }
        
        return {
            'success': True,
            'exported_files': exported_files,
            'timestamp': timestamp,
            'total_stems': len(bus_names)
        }


def _get_mastering_chain_for_genre(genre: str) -> Dict[str, Any]:
    """
    T170: Get mastering chain documentation for manifest.
    
    Returns mastering chain configuration based on genre,
    including target LUFS, devices, and processing order.
    
    Args:
        genre: Musical genre (e.g., 'techno', 'reggaeton', 'house')
    
    Returns:
        Dict with mastering chain configuration
    """
    # Default chains by genre
    mastering_chains = {
        'reggaeton': {
            'preset': 'reggaeton_club',
            'target_lufs': -7.0,
            'ceiling_dbtp': -0.2,
            'chain': [
                {'device': 'Utility', 'params': {'Gain': 0.0, 'Bass Mono': 80.0, 'Width': 1.1}},
                {'device': 'Saturator', 'params': {'Drive': 2.5, 'Type': 'Analog', 'Color': True}},
                {'device': 'Compressor', 'params': {'Threshold': -12.0, 'Ratio': 3.5, 'Attack': 8.0, 'Release': 120.0}},
                {'device': 'EQ Eight', 'params': {'Low_Cut': 30.0, 'Bass_Mono': 80.0}},
                {'device': 'Limiter', 'params': {'Ceiling': -0.2, 'Auto_Release': True}}
            ],
            'notes': 'Reggaeton 95 BPM club mastering - loud, punchy, mono bass below 80Hz',
            'genre_specific': {
                'dem_bow_optimized': True,
                'kick_emphasis': True,
                'sub_bass_mono': True
            }
        },
        'techno': {
            'preset': 'club',
            'target_lufs': -8.0,
            'ceiling_dbtp': -0.3,
            'chain': [
                {'device': 'Utility', 'params': {'Gain': 0.0, 'Bass Mono': 60.0, 'Width': 1.0}},
                {'device': 'Saturator', 'params': {'Drive': 2.0, 'Type': 'Analog', 'Color': True}},
                {'device': 'Compressor', 'params': {'Threshold': -10.0, 'Ratio': 4.0, 'Attack': 10.0, 'Release': 100.0}},
                {'device': 'Limiter', 'params': {'Ceiling': -0.3, 'Auto_Release': True}}
            ],
            'notes': 'Techno club mastering - aggressive saturation, solid low end',
            'genre_specific': {
                'four_on_floor_optimized': True,
                'kick_emphasis': True
            }
        },
        'house': {
            'preset': 'club',
            'target_lufs': -8.0,
            'ceiling_dbtp': -0.3,
            'chain': [
                {'device': 'Utility', 'params': {'Gain': 0.0, 'Bass Mono': 80.0, 'Width': 1.2}},
                {'device': 'Saturator', 'params': {'Drive': 1.5, 'Type': 'Analog', 'Color': True}},
                {'device': 'Compressor', 'params': {'Threshold': -12.0, 'Ratio': 3.0, 'Attack': 15.0, 'Release': 120.0}},
                {'device': 'Limiter', 'params': {'Ceiling': -0.3, 'Auto_Release': True}}
            ],
            'notes': 'House club mastering - balanced, wider stereo field',
            'genre_specific': {
                'disco_influenced': True,
                'vocal_clarity': True
            }
        },
        'tech-house': {
            'preset': 'club',
            'target_lufs': -8.0,
            'ceiling_dbtp': -0.3,
            'chain': [
                {'device': 'Utility', 'params': {'Gain': 0.0, 'Bass Mono': 70.0, 'Width': 1.1}},
                {'device': 'Saturator', 'params': {'Drive': 1.8, 'Type': 'Analog', 'Color': True}},
                {'device': 'Compressor', 'params': {'Threshold': -11.0, 'Ratio': 3.5, 'Attack': 12.0, 'Release': 110.0}},
                {'device': 'Limiter', 'params': {'Ceiling': -0.3, 'Auto_Release': True}}
            ],
            'notes': 'Tech-house club mastering - groove-focused, subtle saturation',
            'genre_specific': {
                'groove_focused': True,
                'bass_weight': True
            }
        },
        'streaming': {
            'preset': 'streaming',
            'target_lufs': -14.0,
            'ceiling_dbtp': -1.0,
            'chain': [
                {'device': 'Utility', 'params': {'Gain': -2.0, 'Bass Mono': 0.0, 'Width': 1.0}},
                {'device': 'Compressor', 'params': {'Threshold': -14.0, 'Ratio': 2.0, 'Attack': 20.0, 'Release': 150.0}},
                {'device': 'Limiter', 'params': {'Ceiling': -1.0, 'Auto_Release': True}}
            ],
            'notes': 'Streaming platform mastering - dynamic, clean',
            'genre_specific': {}
        }
    }
    
    default_chain = {
        'preset': 'safe',
        'target_lufs': -12.0,
        'ceiling_dbtp': -0.5,
        'chain': [
            {'device': 'Utility', 'params': {'Gain': 0.0, 'Bass Mono': 0.0, 'Width': 1.0}},
            {'device': 'Compressor', 'params': {'Threshold': -12.0, 'Ratio': 2.0, 'Attack': 15.0, 'Release': 120.0}},
            {'device': 'Limiter', 'params': {'Ceiling': -0.5, 'Auto_Release': True}}
        ],
        'notes': 'Safe default mastering chain',
        'genre_specific': {}
    }
    
    # Match genre (case-insensitive)
    genre_lower = str(genre).lower() if genre else 'techno'
    
    # Direct match
    if genre_lower in mastering_chains:
        return mastering_chains[genre_lower]
    
    # Partial match (e.g., 'deep-house' -> 'house')
    for key in mastering_chains:
        if key in genre_lower or genre_lower in key:
            return mastering_chains[key]
    
    return default_chain


def get_mastering_preset_for_genre(genre: str) -> Dict[str, Any]:
    """
    Get full mastering preset combining chain and target levels.
    
    Args:
        genre: Musical genre
    
    Returns:
        Dict with full mastering configuration
    """
    chain = _get_mastering_chain_for_genre(genre)
    preset_name = chain.get('preset', 'safe')
    preset_settings = MasteringPreset.get_preset(preset_name)
    
    return {
        'chain': chain,
        'preset': preset_settings,
        'recommended_action': f"Apply {preset_name} preset for {genre}",
        'lufs_target': chain.get('target_lufs', -12.0),
        'ceiling_target': chain.get('ceiling_dbtp', -0.5)
    }
