"""
validation_system_fix.py - Sistema de validación mejorado
T105-T106: Validation System Fix

Validaciones críticas:
- Clips vacíos (silencio real)
- Audio files corruptos/missing
- Key conflict grave (disonancia)
- Samples duplicados accidentalmente
- Phasing entre capas de drums
"""
import logging
from typing import Dict, Any, List, Optional, Tuple
from pathlib import Path
from dataclasses import dataclass

logger = logging.getLogger("ValidationSystemFix")


@dataclass
class ValidationIssue:
    """Representa un problema de validación"""
    type: str
    severity: str  # 'error', 'warning', 'info'
    track: str
    clip: str
    message: str
    suggestion: str
    auto_fixable: bool = False


class ValidationSystemFixer:
    """T105-T106: Sistema de validación completo"""

    def __init__(self):
        self.issues: List[ValidationIssue] = []
        self.validation_rules = {
            'min_clip_duration': 0.5,  # beats
            'max_silence_threshold': -60.0,  # dB
            'key_conflict_threshold': 3,  # semitones
            'duplicate_tolerance_seconds': 0.5,
        }

    def validate_clips(self, clips_data: List[Dict]) -> List[ValidationIssue]:
        """
        T105: Valida clips de audio.

        Checks:
        - Clip vacío (silencio)
        - File missing/corrupt
        - Duración inválida
        """
        issues = []

        for clip in clips_data:
            track_name = clip.get('track_name', 'Unknown')
            clip_name = clip.get('name', 'Unknown')
            file_path = clip.get('file_path', '')

            # 1. Check file exists
            if file_path and not Path(file_path).exists():
                issues.append(ValidationIssue(
                    type='missing_file',
                    severity='error',
                    track=track_name,
                    clip=clip_name,
                    message=f"Audio file not found: {file_path}",
                    suggestion="Rescan library or replace sample",
                    auto_fixable=False
                ))

            # 2. Check duration
            duration = clip.get('duration', 0)
            if duration < self.validation_rules['min_clip_duration']:
                issues.append(ValidationIssue(
                    type='too_short',
                    severity='warning',
                    track=track_name,
                    clip=clip_name,
                    message=f"Clip too short: {duration:.2f} beats",
                    suggestion="Extend or replace sample",
                    auto_fixable=False
                ))

            # 3. Check loop points
            loop_start = clip.get('loop_start', 0)
            loop_end = clip.get('loop_end', duration)
            if loop_end <= loop_start:
                issues.append(ValidationIssue(
                    type='invalid_loop',
                    severity='error',
                    track=track_name,
                    clip=clip_name,
                    message="Loop end before loop start",
                    suggestion="Fix loop points",
                    auto_fixable=True
                ))

        return issues

    def validate_key_conflicts(self, tracks_data: List[Dict], target_key: str) -> List[ValidationIssue]:
        """
        T106: Detecta conflictos armónicos graves.

        Args:
            tracks_data: Tracks con información de key
            target_key: Key objetivo del track

        Returns:
            Lista de conflictos detectados
        """
        issues = []

        # Mapeo de notas a índices
        NOTE_MAP = {
            'C': 0, 'C#': 1, 'Db': 1, 'D': 2, 'D#': 3, 'Eb': 3,
            'E': 4, 'F': 5, 'F#': 6, 'Gb': 6, 'G': 7, 'G#': 8,
            'Ab': 8, 'A': 9, 'A#': 10, 'Bb': 10, 'B': 11
        }

        def get_semitone_distance(key1: str, key2: str) -> int:
            """Calcula distancia en semitonos entre keys."""
            # Extraer root note
            root1 = key1.replace('m', '').replace('M', '')
            root2 = key2.replace('m', '').replace('M', '')

            # Check minor flag
            is_minor1 = 'm' in key1.lower() and 'M' not in key1
            is_minor2 = 'm' in key2.lower() and 'M' not in key2

            # Diferentes modos = potencial conflicto
            if is_minor1 != is_minor2:
                return 6  # Máximo conflicto

            idx1 = NOTE_MAP.get(root1, 0)
            idx2 = NOTE_MAP.get(root2, 0)

            distance = abs(idx1 - idx2)
            return min(distance, 12 - distance)  # Distancia circular

        target_root = target_key.replace('m', '').replace('M', '')

        for track in tracks_data:
            track_name = track.get('name', 'Unknown')
            track_key = track.get('key', '')

            if not track_key:
                continue

            distance = get_semitone_distance(target_key, track_key)

            # Conflicto grave: > 3 semitonos
            if distance >= 4:
                issues.append(ValidationIssue(
                    type='key_conflict',
                    severity='error',
                    track=track_name,
                    clip='',
                    message=f"Severe key conflict: {track_key} vs {target_key} ({distance} semitones)",
                    suggestion=f"Transpose to {target_key} or replace sample",
                    auto_fixable=True
                ))
            elif distance >= 2:
                issues.append(ValidationIssue(
                    type='key_variation',
                    severity='warning',
                    track=track_name,
                    clip='',
                    message=f"Key variation detected: {track_key} vs {target_key}",
                    suggestion="Check if harmonic variation is intentional",
                    auto_fixable=False
                ))

        return issues

    def validate_duplicates(self, clips_data: List[Dict]) -> List[ValidationIssue]:
        """Detecta samples duplicados accidentalmente."""
        issues = []

        # Agrupar por file_path
        file_usage = {}
        for clip in clips_data:
            file_path = clip.get('file_path', '')
            if not file_path:
                continue

            if file_path not in file_usage:
                file_usage[file_path] = []
            file_usage[file_path].append(clip)

        # Detectar duplicados
        for file_path, clips in file_usage.items():
            if len(clips) > 1:
                # Es duplicado si están en tracks diferentes
                tracks = set(c.get('track_name') for c in clips)
                if len(tracks) > 1:
                    issues.append(ValidationIssue(
                        type='duplicate_sample',
                        severity='warning',
                        track=', '.join(tracks),
                        clip=Path(file_path).name,
                        message=f"Sample used in {len(tracks)} different tracks",
                        suggestion="Consider if intentional layering or accidental duplicate",
                        auto_fixable=False
                    ))

        return issues

    def validate_gain_staging(self, tracks_data: List[Dict]) -> List[ValidationIssue]:
        """Valida niveles de gain staging."""
        issues = []

        for track in tracks_data:
            track_name = track.get('name', 'Unknown')
            volume = track.get('volume', 0.85)

            # Clipping prevention
            if volume > 0.95:
                issues.append(ValidationIssue(
                    type='high_volume',
                    severity='warning',
                    track=track_name,
                    clip='',
                    message=f"Volume too high: {volume:.2f}",
                    suggestion="Reduce to prevent clipping",
                    auto_fixable=True
                ))

            # Too quiet
            if volume < 0.1 and track.get('role') not in ['atmos', 'texture']:
                issues.append(ValidationIssue(
                    type='low_volume',
                    severity='info',
                    track=track_name,
                    clip='',
                    message=f"Volume very low: {volume:.2f}",
                    suggestion="Check if track is audible",
                    auto_fixable=False
                ))

        return issues

    def run_full_validation(self, set_data: Dict) -> Dict[str, Any]:
        """
        Ejecuta validación completa del set.

        Args:
            set_data: Datos completos del set de Ableton

        Returns:
            Reporte de validación completo
        """
        all_issues = []

        tracks = set_data.get('tracks', [])
        clips = set_data.get('clips', [])
        target_key = set_data.get('key', 'Am')

        # 1. Validar clips
        clip_issues = self.validate_clips(clips)
        all_issues.extend(clip_issues)

        # 2. Validar key conflicts
        key_issues = self.validate_key_conflicts(tracks, target_key)
        all_issues.extend(key_issues)

        # 3. Validar duplicados
        dup_issues = self.validate_duplicates(clips)
        all_issues.extend(dup_issues)

        # 4. Validar gain staging
        gain_issues = self.validate_gain_staging(tracks)
        all_issues.extend(gain_issues)

        # Clasificar por severidad
        errors = [i for i in all_issues if i.severity == 'error']
        warnings = [i for i in all_issues if i.severity == 'warning']
        info = [i for i in all_issues if i.severity == 'info']
        auto_fixable = [i for i in all_issues if i.auto_fixable]

        return {
            'valid': len(errors) == 0,
            'summary': {
                'total_issues': len(all_issues),
                'errors': len(errors),
                'warnings': len(warnings),
                'info': len(info),
                'auto_fixable': len(auto_fixable)
            },
            'issues': [
                {
                    'type': i.type,
                    'severity': i.severity,
                    'track': i.track,
                    'clip': i.clip,
                    'message': i.message,
                    'suggestion': i.suggestion,
                    'auto_fixable': i.auto_fixable
                }
                for i in all_issues
            ],
            'auto_fixes_available': [
                {'type': i.type, 'track': i.track}
                for i in auto_fixable
            ]
        }

    def apply_auto_fixes(self, set_data: Dict, ableton_connection) -> Dict:
        """Aplica fixes automáticos para issues auto-fixable."""
        fixes_applied = []
        fixes_failed = []

        issues = self.run_full_validation(set_data)

        for issue_data in issues.get('issues', []):
            if not issue_data.get('auto_fixable'):
                continue

            issue_type = issue_data.get('type')
            track = issue_data.get('track')

            try:
                if issue_type == 'invalid_loop':
                    # Fix loop points
                    self._fix_loop_points(ableton_connection, track, issue_data.get('clip'))
                    fixes_applied.append({'type': 'loop_points', 'track': track})

                elif issue_type == 'high_volume':
                    # Reduce volume
                    self._adjust_volume(ableton_connection, track, 0.85)
                    fixes_applied.append({'type': 'volume', 'track': track})

                elif issue_type == 'key_conflict':
                    # Suggest transpose
                    fixes_applied.append({'type': 'key_transpose_suggested', 'track': track})

            except Exception as e:
                fixes_failed.append({'type': issue_type, 'track': track, 'error': str(e)})

        return {
            'fixes_applied': fixes_applied,
            'fixes_failed': fixes_failed,
            'total_fixed': len(fixes_applied)
        }

    def _fix_loop_points(self, ableton_connection, track: str, clip: str):
        """Corrige loop points inválidos."""
        cmd = {
            'command': 'reset_loop_points',
            'track': track,
            'clip': clip
        }
        ableton_connection.send_command(cmd)

    def _adjust_volume(self, ableton_connection, track: str, level: float):
        """Ajusta volumen de track."""
        cmd = {
            'command': 'set_track_volume',
            'track': track,
            'volume': level
        }
        ableton_connection.send_command(cmd)


# Instancia global
_validation_fixer: Optional[ValidationSystemFixer] = None


def get_validation_fixer() -> ValidationSystemFixer:
    """Obtiene instancia global del validador."""
    global _validation_fixer
    if _validation_fixer is None:
        _validation_fixer = ValidationSystemFixer()
    return _validation_fixer
