"""
T222-T100: Auto Improve Set
Regeneración automática de loops con baja densidad/bajo score
"""

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


@dataclass
class SectionScore:
    """Puntuación de una sección del track."""
    section_type: str  # intro, build, drop, break, outro
    start_bar: int
    end_bar: int
    density_score: float  # 0-1
    variation_score: float  # 0-1
    overall_score: float  # 1-5
    issues: List[str]


class AutoImprover:
    """
    Sistema de auto-mejora del set.
    
    T100: Regenera secciones con bajo score sin tocar las que funcionaron bien.
    """
    
    DEFAULT_LOW_SCORE_THRESHOLD = 3.0
    
    def __init__(self, session_id: str, low_score_threshold: float = DEFAULT_LOW_SCORE_THRESHOLD):
        self.session_id = session_id
        self.low_score_threshold = low_score_threshold
        self.manifest = None
        self.scores: List[SectionScore] = []
        
    def analyze_current_set(self) -> Dict[str, Any]:
        """Analiza el set actual y asigna puntuaciones."""
        # Cargar manifest de la generación
        self.manifest = self._load_manifest()
        
        if not self.manifest:
            return {'error': 'No manifest found for session', 'session_id': self.session_id}
        
        # Analizar cada sección
        sections = self.manifest.get('sections', [])
        audio_layers = self.manifest.get('audio_layers', [])
        
        self.scores = []
        
        for section in sections:
            score = self._score_section(section, audio_layers)
            self.scores.append(score)
        
        return {
            'session_id': self.session_id,
            'total_sections': len(self.scores),
            'low_score_sections': len([s for s in self.scores if s.overall_score < self.low_score_threshold]),
            'average_score': sum(s.overall_score for s in self.scores) / len(self.scores) if self.scores else 0,
            'section_scores': [
                {
                    'type': s.section_type,
                    'bars': f"{s.start_bar}-{s.end_bar}",
                    'score': s.overall_score,
                    'issues': s.issues
                }
                for s in self.scores
            ]
        }
    
    def _load_manifest(self) -> Optional[Dict[str, Any]]:
        """Carga el manifest de la sesión."""
        try:
            # Buscar en directorio de manifests
            manifest_dir = os.path.join(
                os.path.dirname(os.path.dirname(__file__)),
                'logs', 'manifests'
            )
            
            manifest_file = os.path.join(manifest_dir, f'{self.session_id}.json')
            
            if os.path.exists(manifest_file):
                with open(manifest_file, 'r') as f:
                    return json.load(f)
            
            # Intentar con get_generation_manifest
            try:
                from ..mcp_wrapper import AbletonMCPWrapper
                wrapper = AbletonMCPWrapper()
                return wrapper._call_tool('ableton-mcp-ai_get_generation_manifest', {})
            except:
                pass
            
            return None
        except Exception as e:
            return {'error': str(e)}
    
    def _score_section(self, section: Dict, audio_layers: List[Dict]) -> SectionScore:
        """Puntúa una sección individual."""
        section_type = section.get('kind', 'unknown')
        start_bar = section.get('start_bar', 0)
        end_bar = section.get('end_bar', start_bar + 16)
        
        # Calcular densidad basada en capas de audio
        layers_in_section = [l for l in audio_layers 
                            if l.get('start_bar', 0) >= start_bar 
                            and l.get('end_bar', end_bar) <= end_bar]
        
        density_score = min(1.0, len(layers_in_section) / 8)  # Normalizar a 8 capas
        
        # Detectar problemas
        issues = []
        
        if density_score < 0.3:
            issues.append('low_density')
        
        if section.get('transition_type') == 'none':
            issues.append('missing_transition')
        
        if section.get('repeat_count', 0) > 4:
            issues.append('excessive_repetition')
        
        # Calcular score basado en problemas
        base_score = 4.0
        
        if 'low_density' in issues:
            base_score -= 1.0
        if 'missing_transition' in issues:
            base_score -= 0.5
        if 'excessive_repetition' in issues:
            base_score -= 0.5
        
        # Bonus por variedad
        if len(layers_in_section) > 4:
            base_score += 0.5
        
        # Ajustar según tipo de sección
        if section_type == 'drop' and density_score < 0.5:
            base_score -= 1.0  # Drops necesitan alta densidad
        
        overall_score = max(1.0, min(5.0, base_score))
        
        return SectionScore(
            section_type=section_type,
            start_bar=start_bar,
            end_bar=end_bar,
            density_score=density_score,
            variation_score=self._calculate_variation(section),
            overall_score=overall_score,
            issues=issues
        )
    
    def _calculate_variation(self, section: Dict) -> float:
        """Calcula score de variación de una sección."""
        # Estimación basada en metadatos
        pattern_count = len(section.get('patterns', []))
        return min(1.0, pattern_count / 4)
    
    def identify_improvement_candidates(self) -> List[Dict[str, Any]]:
        """Identifica secciones candidatas para mejora."""
        candidates = []
        
        for score in self.scores:
            if score.overall_score < self.low_score_threshold:
                candidates.append({
                    'section_type': score.section_type,
                    'start_bar': score.start_bar,
                    'end_bar': score.end_bar,
                    'current_score': score.overall_score,
                    'issues': score.issues,
                    'priority': 'high' if score.overall_score < 2.5 else 'medium'
                })
        
        return sorted(candidates, key=lambda x: x['current_score'])
    
    def generate_improvement_plan(self) -> Dict[str, Any]:
        """Genera plan de mejoras para el set."""
        candidates = self.identify_improvement_candidates()
        
        if not candidates:
            return {
                'status': 'no_improvements_needed',
                'message': 'All sections score above threshold',
                'average_score': sum(s.overall_score for s in self.scores) / len(self.scores) if self.scores else 0
            }
        
        improvements = []
        
        for candidate in candidates:
            improvement = self._plan_section_improvement(candidate)
            improvements.append(improvement)
        
        return {
            'status': 'improvement_plan_generated',
            'session_id': self.session_id,
            'sections_to_improve': len(candidates),
            'improvements': improvements,
            'estimated_duration': f'{len(candidates) * 2} minutes',
            'preserved_sections': len(self.scores) - len(candidates)
        }
    
    def _plan_section_improvement(self, candidate: Dict) -> Dict[str, Any]:
        """Planifica mejoras para una sección específica."""
        issues = candidate['issues']
        actions = []
        
        if 'low_density' in issues:
            actions.append({
                'action': 'add_layers',
                'description': 'Add harmonic and texture layers',
                'count': 3
            })
        
        if 'missing_transition' in issues:
            actions.append({
                'action': 'add_transition_fx',
                'description': 'Add riser/crash FX',
                'types': ['riser', 'crash']
            })
        
        if 'excessive_repetition' in issues:
            actions.append({
                'action': 'vary_pattern',
                'description': 'Apply pattern variation',
                'variation_type': 'breakbeat' if candidate['section_type'] == 'break' else 'fill'
            })
        
        # Recomendaciones específicas por tipo
        if candidate['section_type'] == 'drop':
            actions.append({
                'action': 'enhance_drop',
                'description': 'Add impact and white noise layer'
            })
        
        return {
            'section_type': candidate['section_type'],
            'bars': f"{candidate['start_bar']}-{candidate['end_bar']}",
            'current_score': candidate['current_score'],
            'priority': candidate['priority'],
            'actions': actions,
            'estimated_improvement': min(5.0, candidate['current_score'] + 1.5)
        }
    
    def apply_improvements(self, dry_run: bool = False) -> Dict[str, Any]:
        """Aplica las mejoras planificadas al set."""
        plan = self.generate_improvement_plan()
        
        if plan.get('status') == 'no_improvements_needed':
            return plan
        
        if dry_run:
            return {
                'status': 'dry_run',
                'plan': plan,
                'message': 'Dry run - no changes applied'
            }
        
        results = []
        
        for improvement in plan.get('improvements', []):
            result = self._apply_section_improvement(improvement)
            results.append(result)
        
        return {
            'status': 'improvements_applied',
            'session_id': self.session_id,
            'sections_improved': len(results),
            'results': results,
            'timestamp': datetime.now().isoformat()
        }
    
    def _apply_section_improvement(self, improvement: Dict) -> Dict[str, Any]:
        """Aplica mejoras a una sección específica."""
        # En producción, esto llamaría a MCP tools para modificar el set
        actions = improvement.get('actions', [])
        
        applied_actions = []
        
        for action in actions:
            # Simulación de aplicación
            applied_actions.append({
                'action': action['action'],
                'status': 'simulated',
                'description': action['description']
            })
        
        return {
            'section_type': improvement['section_type'],
            'bars': improvement['bars'],
            'actions_applied': len(applied_actions),
            'applied_actions': applied_actions,
            'predicted_new_score': improvement['estimated_improvement']
        }


def auto_improve_set(session_id: str, low_score_threshold: float = 3.0) -> Dict[str, Any]:
    """
    T100: Auto-mejora del set regenerando secciones con bajo score.
    
    Regenera secciones problemáticas sin tocar las que funcionaron bien.
    
    Args:
        session_id: ID de la sesión a mejorar
        low_score_threshold: Score mínimo aceptable (default 3)
        
    Returns:
        Resultado de la mejora con plan y estatus
    """
    improver = AutoImprover(session_id, low_score_threshold)
    
    # Analizar set actual
    analysis = improver.analyze_current_set()
    
    if 'error' in analysis:
        return analysis
    
    # Generar plan de mejoras
    plan = improver.generate_improvement_plan()
    
    # Aplicar mejoras (o dry run)
    result = improver.apply_improvements(dry_run=False)
    
    return {
        'session_id': session_id,
        'analysis': analysis,
        'improvement_plan': plan,
        'application_result': result,
        'timestamp': datetime.now().isoformat()
    }


if __name__ == '__main__':
    # Test del auto-improver
    result = auto_improve_set('test_session_001', low_score_threshold=3.0)
    print(json.dumps(result, indent=2))
