"""
test_spectral_engine.py - Tests for spectral analysis engine.

T045: Unit tests for spectral_engine.py:
- Test creation without librosa (basic analysis)
- Test similarity between identical profiles (must be 1.0)
- Test similarity between opposite profiles (must be < 0.3)
"""

import unittest
import sys
import os

sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))

try:
    from spectral_engine import SpectralEngine, SpectralProfile, get_spectral_engine
    SPECTRAL_AVAILABLE = True
except ImportError:
    SPECTRAL_AVAILABLE = False
    SpectralEngine = None
    SpectralProfile = None
    get_spectral_engine = None


class TestSpectralEngine(unittest.TestCase):
    """Test cases for SpectralEngine."""
    
    @unittest.skipIf(not SPECTRAL_AVAILABLE, "spectral_engine not available")
    def test_creation_without_librosa(self):
        """Test engine creation works even without librosa."""
        engine = SpectralEngine()
        self.assertIsNotNone(engine)
        self.assertIsNotNone(engine._cache)
    
    @unittest.skipIf(not SPECTRAL_AVAILABLE, "spectral_engine not available")
    def test_similarity_identical_profiles(self):
        """Test that identical profiles have similarity 1.0."""
        engine = SpectralEngine()
        
        profile = SpectralProfile(
            path="/test/sample.wav",
            centroid_mean=2000.0,
            centroid_std=100.0,
            rolloff_85=4000.0,
            flux_mean=0.5,
            mfcc=[0.1, 0.2, 0.3, 0.1, 0.2, 0.3, 0.1, 0.2, 0.3, 0.1, 0.2, 0.3, 0.1],
            rms=0.5,
            spectral_flatness=0.3,
            duration=2.0,
            genre_hints=["test"]
        )
        
        similarity = engine.similarity(profile, profile)
        self.assertAlmostEqual(similarity, 1.0, places=5)
    
    @unittest.skipIf(not SPECTRAL_AVAILABLE, "spectral_engine not available")
    def test_similarity_opposite_profiles(self):
        """Test that opposite profiles have low similarity (< 0.3)."""
        engine = SpectralEngine()
        
        # Low frequency profile (bass-like)
        profile_low = SpectralProfile(
            path="/test/bass.wav",
            centroid_mean=200.0,
            centroid_std=50.0,
            rolloff_85=400.0,
            flux_mean=0.1,
            mfcc=[0.8, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
            rms=0.6,
            spectral_flatness=0.1,
            duration=2.0,
            genre_hints=["bass"]
        )
        
        # High frequency profile (hat-like)
        profile_high = SpectralProfile(
            path="/test/hat.wav",
            centroid_mean=10000.0,
            centroid_std=2000.0,
            rolloff_85=15000.0,
            flux_mean=0.8,
            mfcc=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.8],
            rms=0.2,
            spectral_flatness=0.8,
            duration=0.5,
            genre_hints=["hi_freq_perc"]
        )
        
        similarity = engine.similarity(profile_low, profile_high)
        self.assertLess(similarity, 0.5, f"Expected similarity < 0.5, got {similarity}")
    
    @unittest.skipIf(not SPECTRAL_AVAILABLE, "spectral_engine not available")
    def test_basic_analysis_without_file(self):
        """Test that basic analysis works even for non-existent files."""
        engine = SpectralEngine()
        
        # Basic analysis uses filename heuristics
        profile = engine._analyze_basic("/test/hat_shaker_top.wav")
        self.assertIsNotNone(profile)
        self.assertGreater(profile.centroid_mean, 3000.0)  # High centroid for hat
        
        profile = engine._analyze_basic("/test/bass_sub_808.wav")
        self.assertIsNotNone(profile)
        self.assertLess(profile.centroid_mean, 500.0)  # Low centroid for bass
    
    @unittest.skipIf(not SPECTRAL_AVAILABLE, "spectral_engine not available")
    def test_get_spectral_engine_singleton(self):
        """Test that get_spectral_engine returns singleton."""
        engine1 = get_spectral_engine()
        engine2 = get_spectral_engine()
        self.assertIs(engine1, engine2)
    
    @unittest.skipIf(not SPECTRAL_AVAILABLE, "spectral_engine not available")
    def test_cluster_by_role(self):
        """Test clustering functionality."""
        engine = SpectralEngine()
        
        # Create mock profiles by analyzing basic
        paths = [
            "/test/kick_1.wav",
            "/test/kick_2.wav",
            "/test/hat_1.wav",
            "/test/hat_2.wav",
            "/test/bass_1.wav",
        ]
        
        for p in paths:
            engine.analyze(p)
        
        clusters = engine.cluster_by_role(paths, n_clusters=3)
        self.assertIsNotNone(clusters)
        self.assertIsInstance(clusters, dict)


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