"""
T090-T224: Tracklist Generator con CUE Points
Genera tracklists con timestamps y CUE points para DJs
"""

import json
import os
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional
from dataclasses import dataclass


@dataclass
class CuePoint:
    """Punto CUE para navegación DJ."""
    time: str  # MM:SS
    bar: int
    name: str
    type: str  # 'intro', 'build', 'drop', 'break', 'outro', 'hot'


@dataclass
class TrackEntry:
    """Entrada de track en el tracklist."""
    number: int
    title: str
    artist: str
    genre: str
    bpm: int
    key: str
    start_time: str  # HH:MM:SS
    duration: str  # MM:SS
    cue_points: List[CuePoint]
    energy_level: int
    notes: str


class TracklistGenerator:
    """
    Generador de tracklists profesionales con CUE points.
    
    T090: Genera tracklist con timestamps y CUE points para navegación DJ.
    """
    
    def __init__(self, output_format: str = 'json'):
        self.output_format = output_format
        
    def generate_tracklist(self, format: str = 'json',
                          include_cue_points: bool = True,
                          include_energy_profile: bool = True) -> Dict[str, Any]:
        """
        Genera tracklist completo del set actual.
        
        Args:
            format: 'json', 'text', 'csv', 'cue'
            include_cue_points: Incluir puntos CUE
            include_energy_profile: Incluir perfil de energía
            
        Returns:
            Tracklist con timestamps y CUE points
        """
        # Obtener información del set actual
        set_info = self._get_current_set_info()
        
        if not set_info or 'error' in set_info:
            return {'error': 'No active set found', 'details': set_info}
        
        # Generar entradas de tracks
        tracks = self._generate_track_entries(set_info, include_cue_points)
        
        # Calcular información del set
        total_duration = self._calculate_total_duration(tracks)
        
        tracklist = {
            'metadata': {
                'generated_at': datetime.now().isoformat(),
                'total_tracks': len(tracks),
                'total_duration': total_duration,
                'average_bpm': self._calculate_average_bpm(tracks),
                'key_changes': len(set(t.key for t in tracks)),
                'format': format
            },
            'tracks': [
                {
                    'number': t.number,
                    'title': t.title,
                    'artist': t.artist,
                    'genre': t.genre,
                    'bpm': t.bpm,
                    'key': t.key,
                    'start_time': t.start_time,
                    'duration': t.duration,
                    'energy_level': t.energy_level,
                    'notes': t.notes,
                    'cue_points': [
                        {
                            'time': c.time,
                            'bar': c.bar,
                            'name': c.name,
                            'type': c.type
                        }
                        for c in t.cue_points
                    ] if include_cue_points else []
                }
                for t in tracks
            ],
            'energy_profile': self._generate_energy_profile(tracks) if include_energy_profile else None,
            'transitions': self._analyze_transitions(tracks)
        }
        
        # Exportar en formato solicitado
        if format == 'text':
            tracklist['text_output'] = self._export_text(tracklist)
        elif format == 'csv':
            tracklist['csv_output'] = self._export_csv(tracklist)
        elif format == 'cue':
            tracklist['cue_output'] = self._export_cue(tracklist)
        
        return tracklist
    
    def _get_current_set_info(self) -> Optional[Dict[str, Any]]:
        """Obtiene información del set actual desde Ableton."""
        try:
            # Intentar obtener desde manifest
            from ..mcp_wrapper import AbletonMCPWrapper
            wrapper = AbletonMCPWrapper()
            
            manifest = wrapper._call_tool('ableton-mcp-ai_get_generation_manifest', {})
            if manifest:
                return manifest
            
            # Fallback: información básica de la sesión
            session = wrapper._call_tool('ableton-mcp-ai_get_session_info', {})
            return session
        except Exception as e:
            return {'error': str(e)}
    
    def _generate_track_entries(self, set_info: Dict, 
                               include_cues: bool) -> List[TrackEntry]:
        """Genera entradas de tracks desde información del set."""
        entries = []
        
        # Extraer tracks desde el manifest
        tracks_data = set_info.get('tracks_blueprint', [])
        sections = set_info.get('sections', [])
        
        if not tracks_data:
            # Generar datos de ejemplo basados en secciones
            tracks_data = self._infer_tracks_from_sections(sections)
        
        current_time = 0.0  # segundos
        
        for i, track_data in enumerate(tracks_data):
            # Duración del track
            duration_minutes = track_data.get('duration_minutes', 6.0)
            duration_seconds = duration_minutes * 60
            
            # Generar CUE points
            cue_points = []
            if include_cues:
                cue_points = self._generate_cue_points_for_track(
                    track_data, duration_minutes, current_time
                )
            
            # Crear entrada
            entry = TrackEntry(
                number=i + 1,
                title=track_data.get('name', f'Track {i + 1}'),
                artist=track_data.get('artist', 'AbletonMCP-AI'),
                genre=track_data.get('genre', 'techno'),
                bpm=track_data.get('bpm', 128),
                key=track_data.get('key', 'Am'),
                start_time=self._seconds_to_hhmmss(current_time),
                duration=self._seconds_to_mmss(duration_seconds),
                cue_points=cue_points,
                energy_level=track_data.get('energy_level', 5),
                notes=self._generate_track_notes(track_data)
            )
            
            entries.append(entry)
            current_time += duration_seconds
        
        return entries
    
    def _infer_tracks_from_sections(self, sections: List[Dict]) -> List[Dict]:
        """Infere tracks desde secciones si no hay tracks definidos."""
        if not sections:
            return []
        
        # Agrupar secciones por cambios significativos
        tracks = []
        current_track = {
            'name': 'Track 1',
            'genre': sections[0].get('genre', 'techno'),
            'bpm': sections[0].get('bpm', 128),
            'key': sections[0].get('key', 'Am'),
            'duration_minutes': 0,
            'sections': []
        }
        
        for section in sections:
            # Detectar cambio de track
            if self._is_track_change(section, current_track):
                tracks.append(current_track)
                current_track = {
                    'name': f'Track {len(tracks) + 1}',
                    'genre': section.get('genre', current_track['genre']),
                    'bpm': section.get('bpm', current_track['bpm']),
                    'key': section.get('key', current_track['key']),
                    'duration_minutes': 0,
                    'sections': []
                }
            
            # Agregar sección al track actual
            section_duration = (section.get('end_bar', 0) - section.get('start_bar', 0)) / 4  # 4 beats por bar
            current_track['duration_minutes'] += section_duration
            current_track['sections'].append(section)
        
        # Agregar último track
        if current_track['sections']:
            tracks.append(current_track)
        
        return tracks
    
    def _is_track_change(self, section: Dict, current_track: Dict) -> bool:
        """Detecta si una sección indica cambio de track."""
        # Cambio significativo de BPM
        bpm_diff = abs(section.get('bpm', 128) - current_track.get('bpm', 128))
        if bpm_diff > 5:
            return True
        
        # Cambio de género
        if section.get('genre') != current_track.get('genre'):
            return True
        
        # Sección tipo outro seguida de intro
        if section.get('kind') == 'intro' and current_track.get('sections'):
            last_section = current_track['sections'][-1]
            if last_section.get('kind') == 'outro':
                return True
        
        return False
    
    def _generate_cue_points_for_track(self, track_data: Dict, 
                                      duration_minutes: float,
                                      start_time_seconds: float) -> List[CuePoint]:
        """Genera CUE points para un track."""
        cues = []
        
        # CUE points estándar para tracks electrónicos
        bpm = track_data.get('bpm', 128)
        seconds_per_beat = 60.0 / bpm
        seconds_per_bar = seconds_per_beat * 4
        
        # Intro (bar 1)
        cues.append(CuePoint(
            time='00:00',
            bar=1,
            name='Intro',
            type='intro'
        ))
        
        # Build (aprox 32 bars)
        build_bar = 33
        build_time = (build_bar - 1) * seconds_per_bar
        cues.append(CuePoint(
            time=self._seconds_to_mmss(build_time),
            bar=build_bar,
            name='Build Up',
            type='build'
        ))
        
        # Drop (aprox 48 bars)
        drop_bar = 49
        drop_time = (drop_bar - 1) * seconds_per_bar
        cues.append(CuePoint(
            time=self._seconds_to_mmss(drop_time),
            bar=drop_bar,
            name='Drop',
            type='drop'
        ))
        
        # Break (aprox 80 bars)
        break_bar = 81
        if duration_minutes > 4:
            break_time = (break_bar - 1) * seconds_per_bar
            cues.append(CuePoint(
                time=self._seconds_to_mmss(break_time),
                bar=break_bar,
                name='Break',
                type='break'
            ))
        
        # Outro (8 bars antes del final)
        total_bars = int(duration_minutes * 60 / seconds_per_bar)
        outro_bar = max(total_bars - 8, 1)
        outro_time = (outro_bar - 1) * seconds_per_bar
        cues.append(CuePoint(
            time=self._seconds_to_mmss(outro_time),
            bar=outro_bar,
            name='Outro',
            type='outro'
        ))
        
        return cues
    
    def _generate_track_notes(self, track_data: Dict) -> str:
        """Genera notas descriptivas para el track."""
        notes = []
        
        if track_data.get('style'):
            notes.append(f"Style: {track_data['style']}")
        
        if track_data.get('structure'):
            notes.append(f"Structure: {track_data['structure']}")
        
        return '; '.join(notes) if notes else 'Auto-generated track'
    
    def _calculate_total_duration(self, tracks: List[TrackEntry]) -> str:
        """Calcula duración total del set."""
        if not tracks:
            return '00:00:00'
        
        last_track = tracks[-1]
        start_parts = last_track.start_time.split(':')
        duration_parts = last_track.duration.split(':')
        
        total_seconds = (int(start_parts[0]) * 3600 + int(start_parts[1]) * 60 + int(start_parts[2])) + \
                       (int(duration_parts[0]) * 60 + int(duration_parts[1]))
        
        return self._seconds_to_hhmmss(total_seconds)
    
    def _calculate_average_bpm(self, tracks: List[TrackEntry]) -> float:
        """Calcula BPM promedio."""
        if not tracks:
            return 0
        return sum(t.bpm for t in tracks) / len(tracks)
    
    def _generate_energy_profile(self, tracks: List[TrackEntry]) -> List[Dict]:
        """Genera perfil de energía del set."""
        profile = []
        
        for track in tracks:
            time_parts = track.start_time.split(':')
            minutes = int(time_parts[0]) * 60 + int(time_parts[1])
            
            profile.append({
                'time_minutes': minutes,
                'track_number': track.number,
                'energy_level': track.energy_level
            })
        
        return profile
    
    def _analyze_transitions(self, tracks: List[TrackEntry]) -> List[Dict]:
        """Analiza transiciones entre tracks."""
        transitions = []
        
        for i in range(len(tracks) - 1):
            current = tracks[i]
            next_track = tracks[i + 1]
            
            bpm_change = next_track.bpm - current.bpm
            key_change = next_track.key != current.key
            energy_change = next_track.energy_level - current.energy_level
            
            transition_type = 'smooth'
            if abs(bpm_change) > 5:
                transition_type = 'ramp'
            elif energy_change > 2:
                transition_type = 'build'
            elif energy_change < -2:
                transition_type = 'cooldown'
            elif key_change:
                transition_type = 'key_change'
            
            transitions.append({
                'from_track': current.number,
                'to_track': next_track.number,
                'type': transition_type,
                'bpm_change': bpm_change,
                'energy_change': energy_change,
                'recommendation': self._get_transition_recommendation(transition_type)
            })
        
        return transitions
    
    def _get_transition_recommendation(self, transition_type: str) -> str:
        """Genera recomendación para la transición."""
        recommendations = {
            'smooth': 'Standard crossfade mix',
            'ramp': 'Gradual BPM ramp over 8-16 bars',
            'build': 'Add riser FX before transition',
            'cooldown': 'Allow natural decay, minimal FX',
            'key_change': 'Use harmonic mixing techniques'
        }
        return recommendations.get(transition_type, 'Standard mix')
    
    def _export_text(self, tracklist: Dict) -> str:
        """Exporta tracklist en formato texto."""
        lines = [
            '=' * 60,
            'ABLETONMCP-AI TRACKLIST',
            f'Generated: {tracklist["metadata"]["generated_at"]}',
            f'Total Duration: {tracklist["metadata"]["total_duration"]}',
            '=' * 60,
            ''
        ]
        
        for track in tracklist['tracks']:
            lines.append(f"{track['number']:2d}. {track['start_time']} | {track['artist']} - {track['title']}")
            lines.append(f"    Genre: {track['genre']} | BPM: {track['bpm']} | Key: {track['key']} | Energy: {track['energy_level']}/10")
            
            if track['cue_points']:
                lines.append(f"    CUE Points: {', '.join(c['name'] for c in track['cue_points'])}")
            
            lines.append('')
        
        return '\n'.join(lines)
    
    def _export_csv(self, tracklist: Dict) -> str:
        """Exporta tracklist en formato CSV."""
        import csv
        import io
        
        output = io.StringIO()
        writer = csv.writer(output)
        
        # Header
        writer.writerow(['#', 'Time', 'Artist', 'Title', 'Genre', 'BPM', 'Key', 'Duration', 'Energy'])
        
        # Tracks
        for track in tracklist['tracks']:
            writer.writerow([
                track['number'],
                track['start_time'],
                track['artist'],
                track['title'],
                track['genre'],
                track['bpm'],
                track['key'],
                track['duration'],
                track['energy_level']
            ])
        
        return output.getvalue()
    
    def _export_cue(self, tracklist: Dict) -> str:
        """Exporta tracklist en formato CUE sheet."""
        lines = [
            'TITLE "AbletonMCP-AI DJ Set"',
            'PERFORMER "AbletonMCP-AI"',
            f'REMARK "Generated: {tracklist["metadata"]["generated_at"]}"',
            ''
        ]
        
        for track in tracklist['tracks']:
            time_parts = track['start_time'].split(':')
            cue_time = f"{time_parts[0]}:{time_parts[1]}:{time_parts[2]}"
            
            lines.append(f'TRACK {track["number"]:02d} AUDIO')
            lines.append(f'  TITLE "{track["title"]}"')
            lines.append(f'  PERFORMER "{track["artist"]}"')
            lines.append(f'  INDEX 01 {cue_time}')
            
            # CUE points adicionales como comentarios
            for cue in track.get('cue_points', []):
                lines.append(f'  REM CUE {cue["name"]} at {cue["time"]} (bar {cue["bar"]})')
            
            lines.append('')
        
        return '\n'.join(lines)
    
    def _seconds_to_hhmmss(self, seconds: float) -> str:
        """Convierte segundos a HH:MM:SS."""
        hours = int(seconds // 3600)
        minutes = int((seconds % 3600) // 60)
        secs = int(seconds % 60)
        return f"{hours:02d}:{minutes:02d}:{secs:02d}"
    
    def _seconds_to_mmss(self, seconds: float) -> str:
        """Convierte segundos a MM:SS."""
        minutes = int(seconds // 60)
        secs = int(seconds % 60)
        return f"{minutes:02d}:{secs:02d}"


def generate_tracklist(format: str = 'json') -> Dict[str, Any]:
    """
    T090: Genera tracklist con timestamps y CUE points.
    
    Args:
        format: 'text', 'json', 'csv', 'cue'
        
    Returns:
        Tracklist con timestamps y CUE points para navegación DJ
    """
    generator = TracklistGenerator(output_format=format)
    return generator.generate_tracklist(format=format)


if __name__ == '__main__':
    # Test del generador de tracklists
    for fmt in ['json', 'text', 'csv', 'cue']:
        tracklist = generate_tracklist(format=fmt)
        print(f"\n=== FORMAT: {fmt.upper()} ===")
        
        if fmt == 'text':
            print(tracklist.get('text_output', 'N/A')[:500] + '...')
        elif fmt == 'csv':
            print(tracklist.get('csv_output', 'N/A')[:300] + '...')
        elif fmt == 'cue':
            print(tracklist.get('cue_output', 'N/A')[:400] + '...')
        else:
            print(f"Tracks: {tracklist.get('metadata', {}).get('total_tracks', 0)}")
            print(f"Duration: {tracklist.get('metadata', {}).get('total_duration', 'N/A')}")
