"""
test_melody_generator.py - Tests for melody generation module.

T182-T184: Tests for scale_notes, generate_chord_block, generate_motif, and
generate_reggaeton_harmony functions.
"""

import unittest
import sys
import os

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


class TestMelodyGenerator(unittest.TestCase):
    """Tests for melody_generator module functions."""
    
    def setUp(self):
        """Set up test fixtures."""
        try:
            from melody_generator import (scale_notes, generate_chord_block, 
                                          generate_reggaeton_harmony,
                                          AM_ROOT, AM_SCALE, CHORD_TONES)
            self.scale_notes = scale_notes
            self.generate_chord_block = generate_chord_block
            self.generate_reggaeton_harmony = generate_reggaeton_harmony
            self.AM_ROOT = AM_ROOT
            self.AM_SCALE = AM_SCALE
            self.CHORD_TONES = CHORD_TONES
            self.module_available = True
        except ImportError as e:
            self.module_available = False
            self.skipTest(f"melody_generator module not available: {e}")
    
    def test_scale_notes_returns_correct_length(self):
        """T182: scale_notes(57, 2) should return 14 notes (2 octaves * 7 notes)."""
        if not self.module_available:
            self.skipTest("Module not available")
        
        scale = self.scale_notes(57, 2)
        
        self.assertEqual(len(scale), 14, 
                         f"scale_notes(57, 2) should return 14 notes, got {len(scale)}")
        
        scale_1_oct = self.scale_notes(57, 1)
        self.assertEqual(len(scale_1_oct), 7,
                         f"scale_notes(57, 1) should return 7 notes, got {len(scale_1_oct)}")
        
        scale_3_oct = self.scale_notes(57, 3)
        self.assertEqual(len(scale_3_oct), 21,
                         f"scale_notes(57, 3) should return 21 notes, got {len(scale_3_oct)}")
    
    def test_scale_notes_uses_am_scale_intervals(self):
        """Verify scale_notes uses Am natural scale intervals (A B C D E F G)."""
        if not self.module_available:
            self.skipTest("Module not available")
        
        scale_1_oct = self.scale_notes(57, 1)
        
        expected_intervals = [0, 2, 3, 5, 7, 8, 10]
        actual_intervals = [note - 57 for note in scale_1_oct]
        
        self.assertEqual(actual_intervals, expected_intervals,
                         f"Am scale intervals should be {expected_intervals}, got {actual_intervals}")
    
    def test_chord_block_am(self):
        """T183: generate_chord_block('Am', 0, 4) should return notes in Am pitches."""
        if not self.module_available:
            self.skipTest("Module not available")
        
        notes = self.generate_chord_block('Am', 0, 4)
        
        self.assertGreater(len(notes), 0,
                           "generate_chord_block should return notes")
        
        am_chord_pitches = self.CHORD_TONES.get('Am', [57, 60, 64])
        
        for note in notes:
            pitch = note.pitch if hasattr(note, 'pitch') else note.get('pitch', 0)
            self.assertIn(pitch, am_chord_pitches,
                          f"Note pitch {pitch} not in Am chord tones {am_chord_pitches}")
    
    def test_chord_block_f_major(self):
        """Test generate_chord_block with F major chord."""
        if not self.module_available:
            self.skipTest("Module not available")
        
        notes = self.generate_chord_block('F', 8, 4)
        
        self.assertGreater(len(notes), 0)
        
        f_chord_pitches = self.CHORD_TONES.get('F', [53, 57, 60])
        
        for note in notes:
            pitch = note.pitch if hasattr(note, 'pitch') else note.get('pitch', 0)
            self.assertIn(pitch, f_chord_pitches,
                          f"Note pitch {pitch} not in F chord tones {f_chord_pitches}")
    
    def test_chord_block_arpegio_up_style(self):
        """Test generate_chord_block with arpegio_up style."""
        if not self.module_available:
            self.skipTest("Module not available")
        
        notes = self.generate_chord_block('Am', 0, 4, style='arpegio_up')
        
        self.assertEqual(len(notes), 3,
                         "arpegio_up should have 3 notes (one per chord tone)")
        
        start_times = [n.start_beat if hasattr(n, 'start_beat') else n.get('start_beat', 0) for n in notes]
        for i in range(len(start_times) - 1):
            self.assertLess(start_times[i], start_times[i+1],
                            "arpegio_up notes should be ordered by start time")
    
    def test_chord_block_arpegio_down_style(self):
        """Test generate_chord_block with arpegio_down style."""
        if not self.module_available:
            self.skipTest("Module not available")
        
        notes = self.generate_chord_block('Am', 0, 4, style='arpegio_down')
        
        self.assertEqual(len(notes), 3,
                         "arpegio_down should have 3 notes (one per chord tone)")
    
    def test_generate_reggaeton_harmony_returns_clips(self):
        """T182: generate_reggaeton_harmony should return at least 5 clips."""
        if not self.module_available:
            self.skipTest("Module not available")
        
        harmony = self.generate_reggaeton_harmony(bpm=95.0, total_beats=288.0)
        
        self.assertIsInstance(harmony, dict,
                              "generate_reggaeton_harmony should return a dict")
        
        self.assertIn('tracks', harmony, "harmony should have 'tracks' key")
        self.assertIn('chord_progression', harmony.keys(), 
                      "harmony should have chord_progression")
        
        chord_notes = harmony['tracks']['chords']['clips']
        num_notes = len(chord_notes)
        self.assertGreater(num_notes, 0,
                           f"chords should have notes, got {num_notes}")
    
    def test_generate_reggaeton_harmony_clip_structure(self):
        """Each note in reggaeton harmony should have pitch, start_beat, duration_beats."""
        if not self.module_available:
            self.skipTest("Module not available")
        
        harmony = self.generate_reggaeton_harmony(bpm=95.0, total_beats=288.0)
        
        chord_notes = harmony['tracks']['chords']['clips']
        
        for i, note in enumerate(chord_notes):
            self.assertIn('pitch', note, f"Note {i} should have pitch")
            self.assertIn('start_beat', note, f"Note {i} should have start_beat")
            self.assertIn('duration_beats', note, f"Note {i} should have duration_beats")
    
    def test_generate_reggaeton_harmony_progression(self):
        """Reggaeton harmony should follow Am-F-G-Em progression."""
        if not self.module_available:
            self.skipTest("Module not available")
        
        harmony = self.generate_reggaeton_harmony(bpm=95.0, total_beats=288.0)
        
        chord_prog = harmony.get('chord_progression', [])
        self.assertGreater(len(chord_prog), 0, "Should have chord progression")
        
        chords = [p['chord'] for p in chord_prog]
        
        expected_chords = ['Am', 'F', 'G', 'Em']
        for expected in expected_chords:
            self.assertIn(expected, chords,
                          f"Progression should contain {expected} chord")
    
    def test_generate_reggaeton_harmony_sections(self):
        """Reggaeton harmony should have sections structure."""
        if not self.module_available:
            self.skipTest("Module not available")
        
        harmony = self.generate_reggaeton_harmony(bpm=95.0, total_beats=288.0)
        
        sections = harmony.get('sections', [])
        self.assertGreater(len(sections), 0, "Should have sections")
        
        expected_sections = ['intro', 'drop_a', 'break', 'outro']
        section_names = [s['name'] for s in sections]
        
        for expected in expected_sections:
            self.assertIn(expected, section_names,
                          f"Should have {expected} section")


class TestScaleNotesEdgeCases(unittest.TestCase):
    """Edge case tests for scale_notes function."""
    
    def setUp(self):
        """Set up test fixtures."""
        try:
            from melody_generator import scale_notes, AM_ROOT
            self.scale_notes = scale_notes
            self.AM_ROOT = AM_ROOT
            self.module_available = True
        except ImportError:
            self.module_available = False
            self.skipTest("melody_generator module not available")
    
    def test_zero_octaves_returns_empty(self):
        """scale_notes with 0 octaves should return empty list."""
        if not self.module_available:
            self.skipTest("Module not available")
        
        result = self.scale_notes(self.AM_ROOT, 0)
        self.assertEqual(len(result), 0,
                         "scale_notes(root, 0) should return empty list")
    
    def test_default_octaves(self):
        """scale_notes with default octaves should work."""
        if not self.module_available:
            self.skipTest("Module not available")
        
        result = self.scale_notes(self.AM_ROOT)
        self.assertGreater(len(result), 0,
                           "scale_notes with defaults should return notes")
    
    def test_octave_spacing(self):
        """Notes in consecutive octaves should be 12 semitones apart."""
        if not self.module_available:
            self.skipTest("Module not available")
        
        scale = self.scale_notes(self.AM_ROOT, 2)
        
        first_octave = scale[:7]
        second_octave = scale[7:14]
        
        for i in range(7):
            interval = second_octave[i] - first_octave[i]
            self.assertEqual(interval, 12,
                             f"Octave interval should be 12 semitones, got {interval}")


class TestChordTones(unittest.TestCase):
    """Tests for CHORD_TONES dictionary."""
    
    def setUp(self):
        """Set up test fixtures."""
        try:
            from melody_generator import CHORD_TONES
            self.CHORD_TONES = CHORD_TONES
            self.module_available = True
        except ImportError:
            self.module_available = False
            self.skipTest("melody_generator module not available")
    
    def test_am_chord_tones(self):
        """Am chord should have A, C, E (57, 60, 64)."""
        if not self.module_available:
            self.skipTest("Module not available")
        
        am_tones = self.CHORD_TONES.get('Am')
        self.assertIsNotNone(am_tones, "Am should be in CHORD_TONES")
        self.assertEqual(am_tones, [57, 60, 64],
                         f"Am chord tones should be [57, 60, 64], got {am_tones}")
    
    def test_all_chords_have_three_tones(self):
        """All chords should have exactly 3 tones (triads)."""
        if not self.module_available:
            self.skipTest("Module not available")
        
        for chord_name, tones in self.CHORD_TONES.items():
            self.assertEqual(len(tones), 3,
                             f"Chord {chord_name} should have 3 tones, got {len(tones)}")


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