"""
test_spectral_quality.py - Tests para el módulo spectral_quality (BLOQUE 4)
T181-T195: Calidad Espectral Avanzada y Análisis
"""

import os
import sys
import json
import time
import tempfile
import unittest
import threading
from pathlib import Path
from unittest.mock import Mock, patch, MagicMock

# Añadir paths para imports
SCRIPT_DIR = Path(__file__).parent.parent  # MCP_Server directory
sys.path.insert(0, str(SCRIPT_DIR))

from spectral_quality import (
    # Funciones públicas
    measure_lufs,
    get_streaming_normalization_report,
    get_club_tuning_config,
    get_diagnostics_report,
    analyze_spectral_features,
    extract_transients,
    run_mix_quality_check,
    get_dynamic_eq_config,
    analyze_mixdown_cleanup,
    get_mastering_chain_config,
    run_overlap_safety_audit,
    diagnose_bus_routing,
    rate_generation,
    get_cache_stats,
    start_async_footprint_updater,
    
    # Clases
    FFMPEGLUFSAnalyzer,
    StreamingNormalizationAnalyzer,
    ClubTuningEngine,
    PhaseCorrelationAnalyzer,
    LibrosaAnalyzer,
    TransientExtractor,
    AutomaticQualityChecker,
    DynamicEQCleaner,
    MixdownCleanupAnalyzer,
    MasteringChainConfig,
    OverlapSafetyAuditor,
    BusRCADiagnostician,
    GenerationMemoryFeedback,
    IncrementalIndexCache,
    AsyncSpectralFootprintUpdater,
    SpectralQualityIntegration,
)


class TestT181_FFMPEGLUFSAnalyzer(unittest.TestCase):
    """T181-T083: Tests para medición LUFS con FFMPEG"""
    
    def test_analyzer_instantiation(self):
        """Verifica que el analizador se puede instanciar"""
        analyzer = FFMPEGLUFSAnalyzer()
        self.assertIsNotNone(analyzer)
        self.assertIn("streaming", analyzer.PLATFORM_TARGETS)
        self.assertIn("club", analyzer.PLATFORM_TARGETS)
    
    def test_platform_targets_structure(self):
        """Verifica estructura de targets por plataforma"""
        analyzer = FFMPEGLUFSAnalyzer()
        
        for platform, settings in analyzer.PLATFORM_TARGETS.items():
            self.assertIn("target", settings)
            self.assertIn("true_peak", settings)
            self.assertIn("range", settings)
            self.assertIsInstance(settings["target"], (int, float))
            self.assertIsInstance(settings["true_peak"], (int, float))
    
    def test_fallback_measurement_structure(self):
        """Verifica estructura de medición fallback"""
        analyzer = FFMPEGLUFSAnalyzer()
        
        # Crear archivo wav temporal dummy
        with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as f:
            temp_path = f.name
        
        try:
            measurement = analyzer._estimate_lufs_fallback(temp_path, "streaming")
            
            self.assertIsNotNone(measurement.integrated_lufs)
            self.assertIsNotNone(measurement.true_peak_db)
            self.assertIsInstance(measurement.warnings, list)
            self.assertIsInstance(measurement.compliance, bool)
        finally:
            os.unlink(temp_path)


class TestT182_StreamingNormalization(unittest.TestCase):
    """T092: Tests para normalización multi-plataforma"""
    
    def test_analyzer_platforms(self):
        """Verifica que todas las plataformas están soportadas"""
        analyzer = StreamingNormalizationAnalyzer()
        
        expected_platforms = [
            "spotify", "apple_music", "youtube", "tidal",
            "soundcloud", "bandcamp", "deezer", "amazon_music",
            "club_play"
        ]
        
        for platform in expected_platforms:
            self.assertIn(platform, analyzer.PLATFORM_SETTINGS)
    
    def test_normalization_report_structure(self):
        """Verifica estructura del reporte de normalización"""
        analyzer = StreamingNormalizationAnalyzer()
        
        with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as f:
            temp_path = f.name
        
        try:
            reports = analyzer.analyze_all_platforms(temp_path, current_lufs=-12.0)
            
            for platform, report in reports.items():
                self.assertIsNotNone(report.platform)
                self.assertIsNotNone(report.target_lufs)
                self.assertIsNotNone(report.delta_db)
                self.assertIsInstance(report.will_be_attenuated, bool)
                self.assertIsInstance(report.will_be_amplified, bool)
                self.assertIsNotNone(report.recommendation)
        finally:
            os.unlink(temp_path)


class TestT183_ClubTuning(unittest.TestCase):
    """T084: Tests para tuning de club M/S"""
    
    def test_config_presets(self):
        """Verifica presets de configuración"""
        engine = ClubTuningEngine()
        
        presets = ["standard", "warehouse", "festival"]
        for preset in presets:
            config = engine.get_club_tuning_config(venue_type=preset)
            self.assertIsNotNone(config)
            self.assertGreater(config.sub_bass_freq, 0)
            self.assertGreater(config.side_hp_freq, 0)
            self.assertIsInstance(config.mono_sub, bool)
    
    def test_custom_sub_bass_freq(self):
        """Verifica configuración de frecuencia personalizada"""
        engine = ClubTuningEngine()
        
        config = engine.get_club_tuning_config(sub_bass_freq=100.0)
        self.assertEqual(config.sub_bass_freq, 100.0)
    
    def test_eq_bands_structure(self):
        """Verifica estructura de bandas EQ"""
        engine = ClubTuningEngine()
        config = engine.get_club_tuning_config()
        
        for band in config.eq_bands:
            self.assertIn("freq", band)
            self.assertIn("q", band)
            self.assertIn("gain", band)
            self.assertIn("type", band)


class TestT184_PhaseCorrelation(unittest.TestCase):
    """T088-T089: Tests para correlación de fase"""
    
    def test_analyzer_creation(self):
        """Verifica creación del analizador"""
        analyzer = PhaseCorrelationAnalyzer()
        self.assertIsNotNone(analyzer)
    
    def test_default_report(self):
        """Verifica reporte por defecto"""
        analyzer = PhaseCorrelationAnalyzer()
        report = analyzer._default_report()
        
        self.assertIsNotNone(report.correlation_coefficient)
        self.assertIsNotNone(report.mono_compatibility)
        self.assertIsInstance(report.phase_issues_detected, bool)
        self.assertIsInstance(report.recommendations, list)


class TestT185_LibrosaAnalyzer(unittest.TestCase):
    """T185: Tests para análisis con librosa"""
    
    def test_analyzer_instantiation(self):
        """Verifica que el analizador se inicializa"""
        analyzer = LibrosaAnalyzer()
        self.assertIsNotNone(analyzer)
    
    def test_fallback_analysis(self):
        """Verifica análisis fallback"""
        analyzer = LibrosaAnalyzer()
        
        # Crear wav temporal
        with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as f:
            temp_path = f.name
        
        # Escribir wav minimal
        import wave
        import struct
        try:
            with wave.open(temp_path, 'wb') as wf:
                wf.setnchannels(1)
                wf.setsampwidth(2)
                wf.setframerate(44100)
                # Silencio de 1 segundo
                wf.writeframes(b'\x00' * 88200)
            
            result = analyzer._fallback_analysis(temp_path)
            
            self.assertIsInstance(result, dict)
            self.assertIn("rms_mean", result)
        finally:
            os.unlink(temp_path)


class TestT186_TransientExtractor(unittest.TestCase):
    """T075/T186: Tests para extracción de transientes"""
    
    def test_extractor_creation(self):
        """Verifica creación del extractor"""
        extractor = TransientExtractor()
        self.assertIsNotNone(extractor)
    
    def test_fallback_transients(self):
        """Verifica fallback de transientes"""
        extractor = TransientExtractor()
        
        with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as f:
            temp_path = f.name
        
        try:
            analysis = extractor._fallback_transients(temp_path, tempo=128.0)
            
            self.assertIsNotNone(analysis.onset_times)
            self.assertIsNotNone(analysis.onset_strengths)
            self.assertIsNotNone(analysis.recommended_offsets)
            self.assertIn("kick_offset_ms", analysis.recommended_offsets)
            self.assertIn("bass_offset_ms", analysis.recommended_offsets)
        finally:
            os.unlink(temp_path)


class TestT187_QualityChecker(unittest.TestCase):
    """T085-T087: Tests para quality checker"""
    
    def test_checker_creation(self):
        """Verifica creación del checker"""
        checker = AutomaticQualityChecker()
        self.assertIsNotNone(checker)
    
    def test_thresholds_defined(self):
        """Verifica que los thresholds están definidos"""
        checker = AutomaticQualityChecker()
        
        self.assertIn("lufs_club_range", checker.THRESHOLDS)
        self.assertIn("true_peak_max", checker.THRESHOLDS)
        self.assertIn("correlation_mono_min", checker.THRESHOLDS)
    
    def test_rms_balance_mono(self):
        """Verifica balance RMS para audio mono"""
        checker = AutomaticQualityChecker()
        
        # Crear wav mono temporal
        import wave
        import struct
        
        with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as f:
            temp_path = f.name
        
        try:
            with wave.open(temp_path, 'wb') as wf:
                wf.setnchannels(1)  # Mono
                wf.setsampwidth(2)
                wf.setframerate(44100)
                wf.writeframes(b'\x00\x01' * 44100)
            
            balance = checker._check_rms_balance(temp_path)
            self.assertEqual(balance, 0.0)  # Mono tiene balance 0
        finally:
            os.unlink(temp_path)


class TestT188_DynamicEQ(unittest.TestCase):
    """T094-T095: Tests para EQ dinámico"""
    
    def test_cleaner_creation(self):
        """Verifica creación del cleaner"""
        cleaner = DynamicEQCleaner()
        self.assertIsNotNone(cleaner)
    
    def test_problem_freqs_defined(self):
        """Verifica frecuencias problemáticas definidas"""
        cleaner = DynamicEQCleaner()
        
        expected_freqs = ["mud", "boxiness", "honk", "harsh", "sibilance", "air"]
        for freq in expected_freqs:
            self.assertIn(freq, cleaner.COMMON_PROBLEM_FREQS)
    
    def test_eq_config_structure(self):
        """Verifica estructura de configuración EQ"""
        cleaner = DynamicEQCleaner()
        config = cleaner.get_dynamic_eq_config(
            problem_freqs=["mud", "harsh"],
            side_hp_freq=120.0
        )
        
        self.assertIn("bands", config)
        self.assertIn("ms_processing", config)
        self.assertIn("dynamic_mode", config)
        self.assertTrue(config["ms_processing"])


class TestT189_CleanupAnalyzer(unittest.TestCase):
    """T093: Tests para cleanup analyzer"""
    
    def test_analyzer_creation(self):
        """Verifica creación del analizador"""
        analyzer = MixdownCleanupAnalyzer()
        self.assertIsNotNone(analyzer)
    
    def test_empty_analysis(self):
        """Verifica análisis sin datos"""
        analyzer = MixdownCleanupAnalyzer()
        result = analyzer.analyze_mixdown_cleanup()
        
        self.assertIn("candidates", result)
        self.assertIn("unused_devices", result)
        self.assertIsInstance(result["total_candidates"], int)


class TestT190_MasteringChain(unittest.TestCase):
    """T081: Tests para cadena de mastering"""
    
    def test_chain_creation(self):
        """Verifica creación de la cadena"""
        chain = MasteringChainConfig()
        self.assertIsNotNone(chain)
    
    def test_chains_defined(self):
        """Verifica que hay configuraciones definidas"""
        chain = MasteringChainConfig()
        
        self.assertGreater(len(chain.CHAINS), 0)
    
    def test_techno_club_config(self):
        """Verifica configuración techno club"""
        chain = MasteringChainConfig()
        config = chain.get_mastering_chain_config("techno", "club")
        
        self.assertIn("devices", config)
        self.assertIn("target_lufs", config)
        self.assertIn("genre", config)
        self.assertEqual(config["genre"], "techno")
    
    def test_house_streaming_config(self):
        """Verifica configuración house streaming"""
        chain = MasteringChainConfig()
        config = chain.get_mastering_chain_config("house", "streaming")
        
        self.assertIn("devices", config)
        self.assertEqual(config["target_lufs"], -14)


class TestT191_OverlapAuditor(unittest.TestCase):
    """T096: Tests para overlap auditor"""
    
    def test_auditor_creation(self):
        """Verifica creación del auditor"""
        auditor = OverlapSafetyAuditor()
        self.assertIsNotNone(auditor)
    
    def test_frequency_ranges(self):
        """Verifica rangos de frecuencia"""
        auditor = OverlapSafetyAuditor()
        
        expected_ranges = ["sub", "bass", "low_mid", "mid", "high"]
        for range_name in expected_ranges:
            self.assertIn(range_name, auditor.FREQUENCY_RANGES)


class TestT192_BusDiagnostician(unittest.TestCase):
    """T101-T104: Tests para diagnóstico de buses"""
    
    def test_diagnostician_creation(self):
        """Verifica creación del diagnostician"""
        diag = BusRCADiagnostician()
        self.assertIsNotNone(diag)
    
    def test_bus_mapping(self):
        """Verifica mapeo de buses RCA"""
        diag = BusRCADiagnostician()
        
        self.assertIn("kick", diag.RCA_BUS_MAPPING)
        self.assertIn("bass", diag.RCA_BUS_MAPPING)
        self.assertEqual(diag.RCA_BUS_MAPPING["kick"], "DRUMS_BUS")
        self.assertEqual(diag.RCA_BUS_MAPPING["bass"], "BASS_BUS")
    
    def test_role_detection(self):
        """Verifica detección de roles"""
        diag = BusRCADiagnostician()
        
        self.assertEqual(diag._detect_role("Kick Drum"), "kick")
        self.assertEqual(diag._detect_role("Sub Bass"), "bass")
        # "Lead Synth" tiene ambas keywords, el orden de chequeo determina el resultado
        detected = diag._detect_role("Lead Synth")
        self.assertIn(detected, ["synth", "lead"])  # Ambos son válidos
        self.assertIsNone(diag._detect_role("Unknown Track"))


class TestT193_GenerationMemory(unittest.TestCase):
    """T091: Tests para memoria de generaciones"""
    
    def setUp(self):
        """Setup temporal para tests"""
        self.temp_dir = tempfile.mkdtemp()
        self.memory_file = os.path.join(self.temp_dir, "test_memory.json")
    
    def tearDown(self):
        """Cleanup después de tests"""
        if os.path.exists(self.memory_file):
            os.unlink(self.memory_file)
        os.rmdir(self.temp_dir)
    
    def test_memory_creation(self):
        """Verifica creación de memoria"""
        memory = GenerationMemoryFeedback(memory_file=self.memory_file)
        self.assertIsNotNone(memory)
        self.assertEqual(len(memory.ratings), 0)
    
    def test_rate_generation(self):
        """Verifica rating de generación"""
        memory = GenerationMemoryFeedback(memory_file=self.memory_file)
        
        result = memory.rate_generation(
            session_id="test_001",
            score=4,
            notes="Buena generación",
            genre="techno",
            bpm=128.0,
            key="Am"
        )
        
        self.assertTrue(result["stored"])
        self.assertEqual(result["total_ratings"], 1)
        self.assertEqual(result["average_score"], 4.0)
    
    def test_generate_insights(self):
        """Verifica generación de insights"""
        memory = GenerationMemoryFeedback(memory_file=self.memory_file)
        
        # Agregar algunos ratings
        for i in range(5):
            memory.rate_generation(
                session_id=f"test_{i}",
                score=4 + (i % 2),
                genre="techno" if i < 3 else "house",
                bpm=128.0
            )
        
        insights = memory._generate_insights()
        
        self.assertIn("average_score", insights)
        self.assertIn("trend", insights)
        self.assertIn("preferred_genre", insights)


class TestT194_IncrementalCache(unittest.TestCase):
    """T194: Tests para cache incremental"""
    
    def setUp(self):
        """Setup temporal para tests"""
        self.temp_dir = tempfile.mkdtemp()
    
    def tearDown(self):
        """Cleanup después de tests"""
        import shutil
        shutil.rmtree(self.temp_dir, ignore_errors=True)
    
    def test_cache_creation(self):
        """Verifica creación de cache"""
        cache = IncrementalIndexCache(cache_dir=self.temp_dir)
        self.assertIsNotNone(cache)
        self.assertTrue(os.path.exists(self.temp_dir))
    
    def test_cache_set_get(self):
        """Verifica set y get de cache"""
        cache = IncrementalIndexCache(cache_dir=self.temp_dir)
        
        test_data = {"test": "data", "number": 42}
        cache.set("test_key", test_data)
        
        result = cache.get("test_key")
        self.assertIsNotNone(result)
        self.assertEqual(result["test"], "data")
        self.assertEqual(result["number"], 42)
    
    def test_cache_invalidation(self):
        """Verifica invalidación de cache"""
        cache = IncrementalIndexCache(cache_dir=self.temp_dir)
        
        cache.set("key1", {"data": 1})
        cache.set("key2", {"data": 2})
        
        # Invalidar todo
        cache.invalidate()
        
        # Verificar que se borró
        self.assertIsNone(cache.get("key1"))
        self.assertIsNone(cache.get("key2"))
    
    def test_cache_stats(self):
        """Verifica estadísticas de cache"""
        cache = IncrementalIndexCache(cache_dir=self.temp_dir)
        
        cache.set("key1", {"data": "value1"})
        cache.set("key2", {"data": "value2"})
        
        stats = cache.get_stats()
        
        self.assertIn("entries", stats)
        self.assertIn("total_size_bytes", stats)
        self.assertEqual(stats["entries"], 2)


class TestT195_AsyncUpdater(unittest.TestCase):
    """T195: Tests para updater asíncrono"""
    
    def test_updater_creation(self):
        """Verifica creación del updater"""
        updater = AsyncSpectralFootprintUpdater()
        self.assertIsNotNone(updater)
    
    def test_queue_initially_empty(self):
        """Verifica que la cola inicia vacía"""
        updater = AsyncSpectralFootprintUpdater()
        self.assertEqual(updater.get_queue_size(), 0)


class TestSpectralQualityIntegration(unittest.TestCase):
    """Tests para la integración completa"""
    
    def test_integration_creation(self):
        """Verifica creación de integración"""
        integration = SpectralQualityIntegration()
        self.assertIsNotNone(integration)
        self.assertIsNotNone(integration.lufs_analyzer)
        self.assertIsNotNone(integration.quality_checker)
    
    def test_full_suite_placeholder(self):
        """Verifica estructura de suite completa (placeholder)"""
        # Este test usa datos mock ya que requiere audio real
        integration = SpectralQualityIntegration()
        
        # Verificar que los componentes existen
        self.assertIsNotNone(integration.run_full_quality_suite)


class TestPublicAPI(unittest.TestCase):
    """Tests para la API pública"""
    
    def test_measure_lufs_api(self):
        """Verifica API de measure_lufs"""
        with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as f:
            temp_path = f.name
        
        try:
            # Escribir wav dummy
            import wave
            with wave.open(temp_path, 'wb') as wf:
                wf.setnchannels(2)
                wf.setsampwidth(2)
                wf.setframerate(44100)
                wf.writeframes(b'\x00\x00' * 44100)
            
            result = measure_lufs(temp_path, platform="streaming")
            
            self.assertIn("integrated_lufs", result)
            self.assertIn("true_peak_db", result)
            self.assertIn("compliance", result)
        finally:
            os.unlink(temp_path)
    
    def test_club_tuning_api(self):
        """Verifica API de club tuning"""
        result = get_club_tuning_config(sub_bass_freq=100.0)
        
        self.assertIn("sub_bass_freq", result)
        self.assertEqual(result["sub_bass_freq"], 100.0)
        self.assertIn("eq_bands", result)
    
    def test_mastering_chain_api(self):
        """Verifica API de mastering chain"""
        result = get_mastering_chain_config(genre="techno", platform="club")
        
        self.assertIn("devices", result)
        self.assertIn("target_lufs", result)
        self.assertEqual(result["genre"], "techno")
    
    def test_dynamic_eq_api(self):
        """Verifica API de EQ dinámico"""
        result = get_dynamic_eq_config(problem_freqs="mud,harsh", side_hp_freq=120.0)
        
        self.assertIn("bands", result)
        self.assertIn("ms_processing", result)
    
    def test_diagnostics_api(self):
        """Verifica API de diagnósticos"""
        result = get_diagnostics_report()
        
        self.assertIn("phase_correlation", result)
        self.assertIn("silence_detection", result)
    
    def test_quality_check_api(self):
        """Verifica API de quality check"""
        result = run_mix_quality_check()
        
        self.assertIn("lufs_integrated", result)
        self.assertIn("overall_score", result)
        self.assertIn("passed", result)
    
    def test_cleanup_api(self):
        """Verifica API de cleanup"""
        result = analyze_mixdown_cleanup()
        
        self.assertIn("candidates", result)
        self.assertIn("total_candidates", result)
    
    def test_bus_routing_api(self):
        """Verifica API de diagnóstico de buses"""
        result = diagnose_bus_routing()
        
        # Puede retornar 'issues' o 'error' si no hay runtime
        self.assertTrue(
            "issues" in result or "error" in result,
            "Resultado debe contener 'issues' o 'error'"
        )
    
    def test_cache_stats_api(self):
        """Verifica API de estadísticas de cache"""
        result = get_cache_stats()
        
        self.assertIn("entries", result)
        self.assertIn("cache_dir", result)
    
    def test_rate_generation_api(self):
        """Verifica API de rate generation"""
        # Usar archivo temporal para memoria
        with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
            temp_memory = f.name
        
        try:
            # Crear memory con archivo temporal
            from spectral_quality import GenerationMemoryFeedback
            memory = GenerationMemoryFeedback(memory_file=temp_memory)
            
            result = memory.rate_generation(
                session_id="api_test",
                score=5,
                notes="Excelente"
            )
            
            self.assertTrue(result["stored"])
            self.assertEqual(result["total_ratings"], 1)
        finally:
            os.unlink(temp_memory)


class TestPerformanceAndThreading(unittest.TestCase):
    """Tests de rendimiento y threading"""
    
    def test_cache_thread_safety(self):
        """Verifica seguridad de threads en cache"""
        import tempfile
        import shutil
        
        temp_dir = tempfile.mkdtemp()
        cache = IncrementalIndexCache(cache_dir=temp_dir)
        
        errors = []
        
        def writer_thread(thread_id):
            try:
                for i in range(10):
                    cache.set(f"thread_{thread_id}_key_{i}", {"value": i})
            except Exception as e:
                errors.append(str(e))
        
        threads = []
        for i in range(3):
            t = threading.Thread(target=writer_thread, args=(i,))
            threads.append(t)
            t.start()
        
        for t in threads:
            t.join()
        
        shutil.rmtree(temp_dir, ignore_errors=True)
        
        self.assertEqual(len(errors), 0, f"Errores en threads: {errors}")


def run_tests():
    """Ejecuta todos los tests"""
    # Crear suite
    loader = unittest.TestLoader()
    suite = unittest.TestSuite()
    
    # Agregar todos los tests
    suite.addTests(loader.loadTestsFromTestCase(TestT181_FFMPEGLUFSAnalyzer))
    suite.addTests(loader.loadTestsFromTestCase(TestT182_StreamingNormalization))
    suite.addTests(loader.loadTestsFromTestCase(TestT183_ClubTuning))
    suite.addTests(loader.loadTestsFromTestCase(TestT184_PhaseCorrelation))
    suite.addTests(loader.loadTestsFromTestCase(TestT185_LibrosaAnalyzer))
    suite.addTests(loader.loadTestsFromTestCase(TestT186_TransientExtractor))
    suite.addTests(loader.loadTestsFromTestCase(TestT187_QualityChecker))
    suite.addTests(loader.loadTestsFromTestCase(TestT188_DynamicEQ))
    suite.addTests(loader.loadTestsFromTestCase(TestT189_CleanupAnalyzer))
    suite.addTests(loader.loadTestsFromTestCase(TestT190_MasteringChain))
    suite.addTests(loader.loadTestsFromTestCase(TestT191_OverlapAuditor))
    suite.addTests(loader.loadTestsFromTestCase(TestT192_BusDiagnostician))
    suite.addTests(loader.loadTestsFromTestCase(TestT193_GenerationMemory))
    suite.addTests(loader.loadTestsFromTestCase(TestT194_IncrementalCache))
    suite.addTests(loader.loadTestsFromTestCase(TestT195_AsyncUpdater))
    suite.addTests(loader.loadTestsFromTestCase(TestSpectralQualityIntegration))
    suite.addTests(loader.loadTestsFromTestCase(TestPublicAPI))
    suite.addTests(loader.loadTestsFromTestCase(TestPerformanceAndThreading))
    
    # Ejecutar
    runner = unittest.TextTestRunner(verbosity=2)
    result = runner.run(suite)
    
    return result.wasSuccessful()


if __name__ == "__main__":
    print("=" * 70)
    print("TESTS - SPECTRAL QUALITY MODULE (BLOQUE 4: T181-T195)")
    print("=" * 70)
    print()
    
    success = run_tests()
    
    print()
    print("=" * 70)
    if success:
        print("✓ TODOS LOS TESTS PASARON")
    else:
        print("✗ ALGUNOS TESTS FALLARON")
    print("=" * 70)
    
    sys.exit(0 if success else 1)
