"""
Drum Layer Engine - Professional Dual-Layer Drum System

Combines drum loops from Layer A (reggaeton) and Layer B (SL2025) for
professional layered drum arrangements with synchronized BPM and cross-fading.

Author: AbletonMCP_AI Senior Architecture
"""

from typing import List, Dict, Optional, Tuple
import logging

logger = logging.getLogger(__name__)


class DrumLayerEngine:
    """
    Engine for creating professional dual-layer drum arrangements.
    
    Layer A: Reggaeton drum loops (17 loops)
    Layer B: SL2025 drum loops (30 loops)
    
    Both layers play simultaneously for rich, textured drum sounds.
    """
    
    # Constants
    LAYER_A_COUNT = 17  # Reggaeton loops
    LAYER_B_COUNT = 30  # SL2025 loops
    DEFAULT_BPM = 95
    
    # Density configurations for percussion
    DENSITY_PATTERNS = {
        "low": {"probability": 0.3, "velocity_range": (80, 110)},
        "medium": {"probability": 0.5, "velocity_range": (90, 120)},
        "high": {"probability": 0.7, "velocity_range": (100, 127)},
        "intense": {"probability": 0.85, "velocity_range": (110, 127)}
    }
    
    def __init__(self, live_bridge):
        """
        Initialize DrumLayerEngine with LiveBridge for Ableton integration.
        
        Args:
            live_bridge: LiveBridge instance for creating clips and automation
        """
        self.live_bridge = live_bridge
        self.layer_a_loops: List[str] = []
        self.layer_b_loops: List[str] = []
        self.current_bpm = self.DEFAULT_BPM
        logger.info("DrumLayerEngine initialized")
    
    def create_drum_layers(
        self, 
        track_a: int, 
        track_b: int, 
        loops_a: List[str], 
        loops_b: List[str], 
        positions: List[float]
    ) -> Dict:
        """
        Creates a dual drum layer arrangement with synchronized playback.
        
        Layer A and Layer B play simultaneously for professional drum sound.
        
        Args:
            track_a: Track index for Layer A (reggaeton)
            track_b: Track index for Layer B (SL2025)
            loops_a: List of 17 reggaeton drum loop paths
            loops_b: List of 30 SL2025 drum loop paths
            positions: Bar positions for placing loops [0, 4, 8, 12, ...]
            
        Returns:
            Dict with creation status, clip info, and sync data
        """
        self.layer_a_loops = loops_a
        self.layer_b_loops = loops_b
        
        results = {
            "status": "success",
            "layer_a": {"track": track_a, "clips_created": 0, "clips": []},
            "layer_b": {"track": track_b, "clips_created": 0, "clips": []},
            "synchronized": True,
            "total_clips": 0
        }
        
        # Validate loop counts
        if len(loops_a) != self.LAYER_A_COUNT:
            logger.warning(f"Layer A expected {self.LAYER_A_COUNT} loops, got {len(loops_a)}")
        if len(loops_b) != self.LAYER_B_COUNT:
            logger.warning(f"Layer B expected {self.LAYER_B_COUNT} loops, got {len(loops_b)}")
        
        # Create Layer A clips (reggaeton)
        for i, loop_path in enumerate(loops_a):
            if i < len(positions):
                position = positions[i]
                try:
                    # Calculate warp factor for 95 BPM sync
                    warp_factor = self.synchronize_bpm(loop_path, self.current_bpm)
                    
                    # Create audio clip via LiveBridge
                    clip_result = self._create_loop_clip(
                        track=track_a,
                        loop_path=loop_path,
                        position=position,
                        warp_factor=warp_factor,
                        layer="A",
                        index=i
                    )
                    
                    results["layer_a"]["clips"].append(clip_result)
                    results["layer_a"]["clips_created"] += 1
                    
                except Exception as e:
                    logger.error(f"Failed to create Layer A clip {i}: {e}")
                    results["layer_a"]["clips"].append({"error": str(e), "index": i})
        
        # Create Layer B clips (SL2025) - synchronized to same positions
        for i, loop_path in enumerate(loops_b):
            if i < len(positions):
                position = positions[i]
                try:
                    # Calculate warp factor for 95 BPM sync
                    warp_factor = self.synchronize_bpm(loop_path, self.current_bpm)
                    
                    # Create audio clip via LiveBridge
                    clip_result = self._create_loop_clip(
                        track=track_b,
                        loop_path=loop_path,
                        position=position,
                        warp_factor=warp_factor,
                        layer="B",
                        index=i
                    )
                    
                    results["layer_b"]["clips"].append(clip_result)
                    results["layer_b"]["clips_created"] += 1
                    
                except Exception as e:
                    logger.error(f"Failed to create Layer B clip {i}: {e}")
                    results["layer_b"]["clips"].append({"error": str(e), "index": i})
        
        results["total_clips"] = (
            results["layer_a"]["clips_created"] + 
            results["layer_b"]["clips_created"]
        )
        
        logger.info(
            f"Dual drum layers created: "
            f"A={results['layer_a']['clips_created']}, "
            f"B={results['layer_b']['clips_created']}"
        )
        
        return results
    
    def _create_loop_clip(
        self, 
        track: int, 
        loop_path: str, 
        position: float, 
        warp_factor: float,
        layer: str,
        index: int
    ) -> Dict:
        """
        Helper to create a single loop clip via LiveBridge.
        
        Args:
            track: Track index
            loop_path: Path to audio loop
            position: Bar position
            warp_factor: Time stretch factor for BPM sync
            layer: Layer identifier ("A" or "B")
            index: Loop index
            
        Returns:
            Dict with clip details
        """
        clip_name = f"DrumLayer{layer}_{index:02d}"
        
        # Use LiveBridge to create arrangement clip
        # This calls live_bridge.create_arrangement_audio_clip()
        result = self.live_bridge.create_arrangement_audio_pattern(
            track_index=track,
            file_path=loop_path,
            positions=[position],
            name=clip_name
        )
        
        # Apply warp factor via time stretching if needed
        if warp_factor != 1.0:
            self.live_bridge.time_stretch_clip(
                track_index=track,
                clip_index=0,  # Most recent clip
                factor=warp_factor
            )
        
        return {
            "track": track,
            "position": position,
            "loop": loop_path,
            "warp_factor": warp_factor,
            "name": clip_name,
            "layer": layer,
            "index": index
        }
    
    def synchronize_bpm(self, loop_path: str, target_bpm: int) -> float:
        """
        Calculates warp factor to synchronize a loop to target BPM.
        
        Args:
            loop_path: Path to audio loop file
            target_bpm: Target BPM (default 95)
            
        Returns:
            Float warp factor (1.0 = no change, 0.5 = half speed, 2.0 = double)
        """
        # Default warp factor (no change)
        warp_factor = 1.0
        
        try:
            # Try to analyze loop BPM if possible
            # For now, use metadata or default assumptions
            import os
            
            # Check if file has BPM in filename (common convention)
            filename = os.path.basename(loop_path)
            
            # Try to extract BPM from filename patterns like:
            # "loop_100bpm.wav", "drums_95.wav", "kick_128BPM.aif"
            import re
            
            bpm_match = re.search(r'(\d+)(?:\s*bpm|BPM)', filename, re.IGNORECASE)
            if bpm_match:
                original_bpm = int(bpm_match.group(1))
                warp_factor = original_bpm / target_bpm
                logger.debug(f"Detected BPM {original_bpm} in {filename}, warp={warp_factor:.3f}")
            else:
                # Assume loop is at target BPM already
                warp_factor = 1.0
                logger.debug(f"No BPM detected in {filename}, using warp=1.0")
                
        except Exception as e:
            logger.warning(f"Could not analyze BPM for {loop_path}: {e}")
            warp_factor = 1.0
        
        return warp_factor
    
    def create_percussion_matrix(
        self, 
        track: int, 
        perc_samples: List[str], 
        density: str = "high"
    ) -> Dict:
        """
        Creates complex percussion patterns with variable density.
        
        Args:
            track: Track index for percussion
            perc_samples: List of percussion sample paths
            density: Pattern density - "low", "medium", "high", or "intense"
            
        Returns:
            Dict with matrix creation status and pattern details
        """
        config = self.DENSITY_PATTERNS.get(density, self.DENSITY_PATTERNS["high"])
        probability = config["probability"]
        velocity_min, velocity_max = config["velocity_range"]
        
        results = {
            "status": "success",
            "track": track,
            "density": density,
            "samples_used": len(perc_samples),
            "patterns_created": 0,
            "total_hits": 0,
            "patterns": []
        }
        
        # Create 4-bar percussion matrix
        bars = 4
        beats_per_bar = 4
        total_beats = bars * beats_per_bar
        
        # Standard dembow percussion positions (16th notes)
        # Hi-hats on every 8th note, ghost notes on off-beats
        hihat_positions = [i * 0.5 for i in range(total_beats * 2)]  # 8th notes
        ghost_positions = [i * 0.25 + 0.25 for i in range(total_beats * 4)]  # 16th notes offset
        
        for sample_idx, sample_path in enumerate(perc_samples):
            pattern = {
                "sample": sample_path,
                "sample_index": sample_idx,
                "hits": [],
                "note_count": 0
            }
            
            # Determine instrument type from filename
            sample_type = self._detect_sample_type(sample_path)
            
            if sample_type in ["hihat", "hat", "hihat_closed"]:
                # Place hi-hats at 8th note positions
                for pos in hihat_positions:
                    if self._should_place_hit(probability):
                        velocity = self._random_velocity(velocity_min, velocity_max)
                        pattern["hits"].append({
                            "position": pos,
                            "velocity": velocity,
                            "type": "hihat"
                        })
                        
            elif sample_type in ["shaker", "tambourine", "perc"]:
                # Ghost notes at 16th positions
                for pos in ghost_positions:
                    if self._should_place_hit(probability * 0.7):  # Slightly less dense
                        velocity = self._random_velocity(
                            int(velocity_min * 0.8), 
                            int(velocity_max * 0.9)
                        )
                        pattern["hits"].append({
                            "position": pos,
                            "velocity": velocity,
                            "type": "ghost"
                        })
                        
            elif sample_type in ["conga", "bongo", "timbale"]:
                # Latin percussion - syncopated patterns
                conga_positions = [0, 1.5, 2.5, 3, 3.5]  # Classic tumbao pattern
                for bar in range(bars):
                    for pos in conga_positions:
                        actual_pos = (bar * beats_per_bar) + pos
                        if self._should_place_hit(probability):
                            velocity = self._random_velocity(velocity_min, velocity_max)
                            pattern["hits"].append({
                                "position": actual_pos,
                                "velocity": velocity,
                                "type": "conga"
                            })
            
            pattern["note_count"] = len(pattern["hits"])
            results["patterns"].append(pattern)
            results["total_hits"] += pattern["note_count"]
            
            if pattern["note_count"] > 0:
                results["patterns_created"] += 1
        
        logger.info(
            f"Percussion matrix created: {results['patterns_created']} patterns, "
            f"{results['total_hits']} total hits at {density} density"
        )
        
        return results
    
    def _detect_sample_type(self, sample_path: str) -> str:
        """Detect percussion type from filename."""
        import os
        filename = os.path.basename(sample_path).lower()
        
        if any(x in filename for x in ["hihat", "hat", "hh"]):
            return "hihat"
        elif any(x in filename for x in ["shaker", "shake"]):
            return "shaker"
        elif any(x in filename for x in ["tamb", "tambourine"]):
            return "tambourine"
        elif any(x in filename for x in ["conga", "tumbao"]):
            return "conga"
        elif any(x in filename for x in ["bongo", "bng"]):
            return "bongo"
        elif any(x in filename for x in ["timbale", "timbales"]):
            return "timbale"
        else:
            return "perc"
    
    def _should_place_hit(self, probability: float) -> bool:
        """Determine if a hit should be placed based on probability."""
        import random
        return random.random() < probability
    
    def _random_velocity(self, min_vel: int, max_vel: int) -> int:
        """Generate random velocity within range."""
        import random
        return random.randint(min_vel, max_vel)
    
    def build_drum_breaks(
        self, 
        track: int, 
        break_samples: List[str], 
        section_positions: Dict[str, List[float]]
    ) -> Dict:
        """
        Places drum breaks at specific song sections with cross-fading.
        
        Args:
            track: Track index for breaks
            break_samples: List of break sample paths
            section_positions: Dict mapping section names to bar positions
                             e.g., {"intro": [0], "verse": [8, 24], "chorus": [16, 32]}
            
        Returns:
            Dict with break placement and cross-fade configuration
        """
        results = {
            "status": "success",
            "track": track,
            "breaks_placed": 0,
            "sections": {},
            "crossfades": []
        }
        
        sample_idx = 0
        
        for section_name, positions in section_positions.items():
            results["sections"][section_name] = {
                "positions": positions,
                "breaks": []
            }
            
            for pos in positions:
                if sample_idx >= len(break_samples):
                    sample_idx = 0  # Cycle through samples
                
                sample_path = break_samples[sample_idx]
                
                # Calculate warp for 95 BPM
                warp_factor = self.synchronize_bpm(sample_path, self.current_bpm)
                
                # Create break clip
                break_clip = self._create_break_clip(
                    track=track,
                    sample_path=sample_path,
                    position=pos,
                    warp_factor=warp_factor,
                    section=section_name
                )
                
                results["sections"][section_name]["breaks"].append(break_clip)
                results["breaks_placed"] += 1
                sample_idx += 1
        
        # Configure cross-fading between consecutive breaks
        all_breaks = []
        for section_data in results["sections"].values():
            all_breaks.extend(section_data["breaks"])
        
        # Sort by position
        all_breaks.sort(key=lambda x: x["position"])
        
        # Create cross-fades between breaks
        for i in range(len(all_breaks) - 1):
            current = all_breaks[i]
            next_break = all_breaks[i + 1]
            
            crossfade = self._create_crossfade(
                track=track,
                clip_a_idx=i,
                clip_b_idx=i + 1,
                fade_start=current["position"] + 3.5,  # Start fade before end
                fade_duration=0.5  # 2-beat fade
            )
            
            results["crossfades"].append(crossfade)
        
        logger.info(
            f"Drum breaks built: {results['breaks_placed']} breaks, "
            f"{len(results['crossfades'])} crossfades"
        )
        
        return results
    
    def _create_break_clip(
        self, 
        track: int, 
        sample_path: str, 
        position: float, 
        warp_factor: float,
        section: str
    ) -> Dict:
        """Create a single drum break clip."""
        clip_name = f"Break_{section}_{int(position)}"
        
        # Create via LiveBridge
        self.live_bridge.create_arrangement_audio_pattern(
            track_index=track,
            file_path=sample_path,
            positions=[position],
            name=clip_name
        )
        
        # Apply warp if needed
        if warp_factor != 1.0:
            self.live_bridge.time_stretch_clip(
                track_index=track,
                clip_index=0,
                factor=warp_factor
            )
        
        return {
            "track": track,
            "position": position,
            "sample": sample_path,
            "warp_factor": warp_factor,
            "section": section,
            "name": clip_name
        }
    
    def _create_crossfade(
        self, 
        track: int, 
        clip_a_idx: int, 
        clip_b_idx: int,
        fade_start: float, 
        fade_duration: float
    ) -> Dict:
        """
        Creates cross-fade automation between two break clips.
        
        Args:
            track: Track index
            clip_a_idx: Index of first clip (fades out)
            clip_b_idx: Index of second clip (fades in)
            fade_start: Start position in bars
            fade_duration: Fade duration in bars
            
        Returns:
            Dict with crossfade configuration
        """
        fade_end = fade_start + fade_duration
        
        # Volume automation for clip A (fade out)
        volume_points_a = [
            [fade_start * 4, 0.8],      # Start at 80% volume (beats)
            [fade_end * 4, 0.0]         # Fade to 0%
        ]
        
        # Volume automation for clip B (fade in)
        volume_points_b = [
            [fade_start * 4, 0.0],      # Start at 0%
            [fade_end * 4, 0.8]         # Fade to 80%
        ]
        
        # Apply via LiveBridge if available
        try:
            self.live_bridge.add_parameter_automation(
                track_index=track,
                parameter_name="volume",
                points=volume_points_a
            )
        except Exception as e:
            logger.debug(f"Could not apply crossfade A: {e}")
        
        return {
            "track": track,
            "clip_a": clip_a_idx,
            "clip_b": clip_b_idx,
            "fade_start": fade_start,
            "fade_end": fade_end,
            "duration": fade_duration,
            "type": "volume_crossfade"
        }
    
    def get_layer_statistics(self) -> Dict:
        """
        Returns statistics about current drum layers.
        
        Returns:
            Dict with layer counts, BPM, and sync status
        """
        return {
            "layer_a_count": len(self.layer_a_loops),
            "layer_b_count": len(self.layer_b_loops),
            "expected_a": self.LAYER_A_COUNT,
            "expected_b": self.LAYER_B_COUNT,
            "current_bpm": self.current_bpm,
            "sync_enabled": True,
            "total_capacity": self.LAYER_A_COUNT + self.LAYER_B_COUNT
        }
    
    def validate_loop_compatibility(self, loops_a: List[str], loops_b: List[str]) -> Dict:
        """
        Validates that loops are compatible for dual-layer playback.
        
        Args:
            loops_a: Layer A loop paths
            loops_b: Layer B loop paths
            
        Returns:
            Dict with validation results
        """
        import os
        
        issues = []
        warnings = []
        
        # Check file existence
        for loop in loops_a:
            if not os.path.exists(loop):
                issues.append(f"Layer A loop not found: {loop}")
        
        for loop in loops_b:
            if not os.path.exists(loop):
                issues.append(f"Layer B loop not found: {loop}")
        
        # Check file formats
        valid_exts = ['.wav', '.aif', '.aiff', '.mp3', '.ogg']
        for loop in loops_a + loops_b:
            ext = os.path.splitext(loop)[1].lower()
            if ext not in valid_exts:
                warnings.append(f"Unusual format {ext}: {loop}")
        
        return {
            "valid": len(issues) == 0,
            "issues": issues,
            "warnings": warnings,
            "layer_a_valid": len([i for i in issues if "Layer A" in i]) == 0,
            "layer_b_valid": len([i for i in issues if "Layer B" in i]) == 0
        }
