"""
test_spectral_integration.py - Tests de integracion para SpectralEngine.

Valida T018-T043: sintesis granular, clusters tímbricos, análisis espectral.
"""

import json
import os
import sys
import unittest
from pathlib import Path
from unittest.mock import MagicMock, patch

SCRIPT_DIR = Path(__file__).resolve().parent
SERVER_DIR = SCRIPT_DIR.parent
if str(SERVER_DIR) not in sys.path:
    sys.path.insert(0, str(SERVER_DIR))


class TestSpectralProfile(unittest.TestCase):
    """Tests para la clase SpectralProfile."""

    def test_spectral_profile_creation(self):
        """SpectralProfile se crea correctamente con todos los campos."""
        from spectral_engine import SpectralProfile
        
        profile = SpectralProfile(
            path="/test/sample.wav",
            centroid_mean=2500.0,
            centroid_std=300.0,
            rolloff_85=5000.0,
            flux_mean=0.15,
            mfcc=[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0],
            rms=0.25,
            spectral_flatness=0.5,
            duration=4.0,
            genre_hints=["techno", "kick"]
        )
        
        self.assertEqual(profile.path, "/test/sample.wav")
        self.assertEqual(profile.centroid_mean, 2500.0)
        self.assertEqual(profile.centroid_std, 300.0)
        self.assertEqual(profile.rolloff_85, 5000.0)
        self.assertEqual(profile.flux_mean, 0.15)
        self.assertEqual(len(profile.mfcc), 13)
        self.assertEqual(profile.rms, 0.25)
        self.assertEqual(profile.spectral_flatness, 0.5)
        self.assertEqual(profile.duration, 4.0)
        self.assertIn("techno", profile.genre_hints)

    def test_spectral_profile_to_dict(self):
        """SpectralProfile se serializa correctamente."""
        from spectral_engine import SpectralProfile
        
        profile = SpectralProfile(
            path="/test/sample.wav",
            centroid_mean=2500.0,
            centroid_std=300.0,
            rolloff_85=5000.0,
            flux_mean=0.15,
            mfcc=[1.0] * 13,
            rms=0.25,
            spectral_flatness=0.5,
            duration=4.0,
            genre_hints=["techno"]
        )
        
        d = profile.__dict__.copy()
        self.assertIsInstance(d, dict)
        self.assertIn("path", d)
        self.assertIn("centroid_mean", d)
        self.assertIn("mfcc", d)


class TestSpectralEngineInit(unittest.TestCase):
    """Tests para inicializacion del SpectralEngine."""

    def test_spectral_engine_init_no_librosa(self):
        """SpectralEngine inicializa correctamente sin librosa."""
        with patch.dict('sys.modules', {'librosa': None}):
            from spectral_engine import SpectralEngine
            engine = SpectralEngine()
            self.assertIsNotNone(engine._cache)
            self.assertIsInstance(engine._cache, dict)

    def test_spectral_engine_cache_structure(self):
        """El cache tiene estructura correcta."""
        from spectral_engine import SpectralEngine
        
        engine = SpectralEngine()
        self.assertIsInstance(engine._cache, dict)


class TestSpectralSimilarity(unittest.TestCase):
    """Tests para calculo de similitud espectral."""

    def test_similarity_identical_profiles(self):
        """Perfiles identicos tienen similitud cercana a 1.0."""
        from spectral_engine import SpectralEngine, SpectralProfile
        
        engine = SpectralEngine()
        
        profile = SpectralProfile(
            path="/test/sample.wav",
            centroid_mean=2500.0,
            centroid_std=300.0,
            rolloff_85=5000.0,
            flux_mean=0.15,
            mfcc=[1.0] * 13,
            rms=0.25,
            spectral_flatness=0.5,
            duration=4.0,
            genre_hints=["techno"]
        )
        
        similarity = engine.similarity(profile, profile)
        self.assertGreater(similarity, 0.9)
        self.assertLessEqual(similarity, 1.0)

    def test_similarity_different_profiles(self):
        """Perfiles diferentes tienen similitud menor."""
        from spectral_engine import SpectralEngine, SpectralProfile
        
        engine = SpectralEngine()
        
        profile_a = SpectralProfile(
            path="/test/a.wav",
            centroid_mean=2500.0,
            centroid_std=300.0,
            rolloff_85=5000.0,
            flux_mean=0.15,
            mfcc=[1.0] * 13,
            rms=0.25,
            spectral_flatness=0.5,
            duration=4.0,
            genre_hints=["techno"]
        )
        
        profile_b = SpectralProfile(
            path="/test/b.wav",
            centroid_mean=8000.0,
            centroid_std=1500.0,
            rolloff_85=15000.0,
            flux_mean=0.5,
            mfcc=[5.0] * 13,
            rms=0.1,
            spectral_flatness=0.8,
            duration=2.0,
            genre_hints=["ambient"]
        )
        
        similarity = engine.similarity(profile_a, profile_b)
        self.assertGreater(similarity, 0.0)
        self.assertLess(similarity, 0.8)

    def test_similarity_null_profiles(self):
        """Similitud con perfiles nulos retorna 0.0."""
        from spectral_engine import SpectralEngine, SpectralProfile
        
        engine = SpectralEngine()
        
        valid_profile = SpectralProfile(
            path="/test/sample.wav",
            centroid_mean=2500.0,
            centroid_std=300.0,
            rolloff_85=5000.0,
            flux_mean=0.15,
            mfcc=[1.0] * 13,
            rms=0.25,
            spectral_flatness=0.5,
            duration=4.0,
            genre_hints=["techno"]
        )
        
        self.assertEqual(engine.similarity(None, valid_profile), 0.0)
        self.assertEqual(engine.similarity(valid_profile, None), 0.0)
        self.assertEqual(engine.similarity(None, None), 0.0)


class TestSpectralAnalysis(unittest.TestCase):
    """Tests para analisis de samples."""

    def test_analyze_from_cache(self):
        """Analisis usa cache cuando el sample ya fue analizado."""
        from spectral_engine import SpectralEngine, SpectralProfile
        
        engine = SpectralEngine()
        
        cached_profile = SpectralProfile(
            path="/cached/sample.wav",
            centroid_mean=2500.0,
            centroid_std=300.0,
            rolloff_85=5000.0,
            flux_mean=0.15,
            mfcc=[1.0] * 13,
            rms=0.25,
            spectral_flatness=0.5,
            duration=4.0,
            genre_hints=["techno"]
        )
        
        engine._cache["/cached/sample.wav"] = cached_profile
        
        result = engine.analyze("/cached/sample.wav")
        self.assertIsNotNone(result)
        self.assertEqual(result.path, "/cached/sample.wav")


class TestFindSimilarSamples(unittest.TestCase):
    """Tests para busqueda de samples similares."""

    def test_find_most_similar_empty_candidates(self):
        """Busqueda con lista vacia retorna lista vacia."""
        from spectral_engine import SpectralEngine
        
        engine = SpectralEngine()
        result = engine.find_most_similar("/test/ref.wav", [], top_n=5)
        self.assertEqual(result, [])

    def test_find_most_similar_uses_similarity(self):
        """find_most_similar usa funcion de similitud correctamente."""
        from spectral_engine import SpectralEngine, SpectralProfile
        
        engine = SpectralEngine()
        
        ref_profile = SpectralProfile(
            path="/ref.wav",
            centroid_mean=2500.0,
            centroid_std=300.0,
            rolloff_85=5000.0,
            flux_mean=0.15,
            mfcc=[1.0] * 13,
            rms=0.25,
            spectral_flatness=0.5,
            duration=4.0,
            genre_hints=["techno"]
        )
        
        similar_profile = SpectralProfile(
            path="/similar.wav",
            centroid_mean=2600.0,
            centroid_std=320.0,
            rolloff_85=5100.0,
            flux_mean=0.16,
            mfcc=[1.1] * 13,
            rms=0.26,
            spectral_flatness=0.52,
            duration=4.1,
            genre_hints=["techno"]
        )
        
        different_profile = SpectralProfile(
            path="/different.wav",
            centroid_mean=8000.0,
            centroid_std=1500.0,
            rolloff_85=15000.0,
            flux_mean=0.8,
            mfcc=[8.0] * 13,
            rms=0.1,
            spectral_flatness=0.9,
            duration=2.0,
            genre_hints=["ambient"]
        )
        
        engine._cache["/ref.wav"] = ref_profile
        engine._cache["/similar.wav"] = similar_profile
        engine._cache["/different.wav"] = different_profile
        
        result = engine.find_most_similar("/ref.wav", ["/similar.wav", "/different.wav"], top_n=2)
        
        self.assertEqual(len(result), 2)
        self.assertEqual(result[0][0], "/similar.wav")
        self.assertGreater(result[0][1], result[1][1])


class TestSpectralGenreHints(unittest.TestCase):
    """Tests para deteccion de generos por perfil espectral."""

    def test_kick_drum_centroid_range(self):
        """Kick drums tienen centroides bajos (100-500 Hz)."""
        from spectral_engine import SpectralProfile
        
        kick_profile = SpectralProfile(
            path="/kicks/punchy.wav",
            centroid_mean=250.0,
            centroid_std=80.0,
            rolloff_85=800.0,
            flux_mean=0.05,
            mfcc=[0.5] * 13,
            rms=0.8,
            spectral_flatness=0.2,
            duration=0.5,
            genre_hints=["kick", "techno"]
        )
        
        self.assertLess(kick_profile.centroid_mean, 500.0)
        self.assertLess(kick_profile.rolloff_85, 1500.0)

    def test_synth_centroid_range(self):
        """Synth pads tienen centroides medios-altos (500-5000 Hz)."""
        from spectral_engine import SpectralProfile
        
        synth_profile = SpectralProfile(
            path="/synths/pad.wav",
            centroid_mean=2000.0,
            centroid_std=400.0,
            rolloff_85=8000.0,
            flux_mean=0.02,
            mfcc=[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0],
            rms=0.4,
            spectral_flatness=0.7,
            duration=8.0,
            genre_hints=["synth", "pad"]
        )
        
        self.assertGreater(synth_profile.centroid_mean, 500.0)
        self.assertLess(synth_profile.centroid_mean, 5000.0)


class TestSpectralIntegration(unittest.TestCase):
    """Tests de integracion completa del modulo spectral_engine."""

    def test_full_analysis_workflow(self):
        """Workflow completo: analisis de referencia y busqueda de similares."""
        from spectral_engine import SpectralEngine, SpectralProfile
        
        engine = SpectralEngine()
        
        samples = []
        for i in range(5):
            profile = SpectralProfile(
                path=f"/test/sample_{i}.wav",
                centroid_mean=1500.0 + i * 500,
                centroid_std=300.0,
                rolloff_85=4000.0 + i * 1000,
                flux_mean=0.1 + i * 0.05,
                mfcc=[float(i)] * 13,
                rms=0.3,
                spectral_flatness=0.5,
                duration=4.0,
                genre_hints=["techno"]
            )
            engine._cache[f"/test/sample_{i}.wav"] = profile
            samples.append(f"/test/sample_{i}.wav")
        
        similar = engine.find_most_similar(samples[0], samples[1:], top_n=3)
        
        self.assertEqual(len(similar), 3)
        self.assertTrue(all(s[1] >= 0.0 for s in similar))


if __name__ == "__main__":
    unittest.main()