"""Unit tests for core utility functions."""
import unittest
import tempfile
import time
from pathlib import Path

from reaper_mcp.core.util import (
    _validate_audio_params,
    is_cache_valid,
    _ensure_output_dir,
    _generate_timestamped_filename,
    Note,
)


class TestValidateAudioParams(unittest.TestCase):
    """Tests for _validate_audio_params function."""
    
    def test_valid_frequency(self):
        """Test validation with valid frequency."""
        error = _validate_audio_params(frequency=440.0)
        self.assertIsNone(error)
    
    def test_invalid_frequency_negative(self):
        """Test validation with negative frequency."""
        error = _validate_audio_params(frequency=-10.0)
        self.assertIsNotNone(error)
        self.assertIn("Frequency", error)
    
    def test_invalid_frequency_too_high(self):
        """Test validation with too high frequency."""
        error = _validate_audio_params(frequency=25000.0)
        self.assertIsNotNone(error)
        self.assertIn("Frequency", error)
    
    def test_valid_duration(self):
        """Test validation with valid duration."""
        error = _validate_audio_params(duration=2.0)
        self.assertIsNone(error)
    
    def test_invalid_duration_negative(self):
        """Test validation with negative duration."""
        error = _validate_audio_params(duration=-1.0)
        self.assertIsNotNone(error)
        self.assertIn("Duration", error)
    
    def test_invalid_duration_too_long(self):
        """Test validation with too long duration."""
        error = _validate_audio_params(duration=400.0)
        self.assertIsNotNone(error)
        self.assertIn("Duration", error)
    
    def test_valid_amplitude(self):
        """Test validation with valid amplitude."""
        error = _validate_audio_params(amplitude=0.5)
        self.assertIsNone(error)
    
    def test_invalid_amplitude_negative(self):
        """Test validation with negative amplitude."""
        error = _validate_audio_params(amplitude=-0.1)
        self.assertIsNotNone(error)
        self.assertIn("Amplitude", error)
    
    def test_invalid_amplitude_too_high(self):
        """Test validation with too high amplitude."""
        error = _validate_audio_params(amplitude=1.5)
        self.assertIsNotNone(error)
        self.assertIn("Amplitude", error)
    
    def test_all_valid_params(self):
        """Test validation with all valid parameters."""
        error = _validate_audio_params(frequency=440.0, duration=2.0, amplitude=0.8)
        self.assertIsNone(error)
    
    def test_multiple_invalid_params(self):
        """Test that first error is returned when multiple params are invalid."""
        error = _validate_audio_params(frequency=-10.0, duration=-1.0)
        self.assertIsNotNone(error)


class TestIsCacheValid(unittest.TestCase):
    """Tests for is_cache_valid function."""
    
    def setUp(self):
        """Set up test file."""
        self.temp_dir = tempfile.mkdtemp()
        self.test_file = Path(self.temp_dir, "test_file.txt")
        with open(self.test_file, 'w') as f:
            f.write("test")
    
    def tearDown(self):
        """Clean up temporary files."""
        if Path(self.test_file).exists():
            Path(self.test_file).unlink()
        Path(self.temp_dir).rmdir()
    
    def test_cache_valid_with_correct_mtime(self):
        """Test cache is valid when mtime matches."""
        mtime = Path(self.test_file).stat().st_mtime
        self.assertTrue(is_cache_valid(self.test_file, mtime))
    
    def test_cache_invalid_with_none_mtime(self):
        """Test cache is invalid when cached_mtime is None."""
        self.assertFalse(is_cache_valid(self.test_file, None))
    
    def test_cache_invalid_with_different_mtime(self):
        """Test cache is invalid when mtime differs."""
        old_mtime = Path(self.test_file).stat().st_mtime - 10.0
        self.assertFalse(is_cache_valid(self.test_file, old_mtime))
    
    def test_cache_invalid_nonexistent_file(self):
        """Test cache is invalid for nonexistent file."""
        self.assertFalse(is_cache_valid("/nonexistent/file.txt", 123456.0))
    
    def test_cache_valid_with_small_time_difference(self):
        """Test cache is valid with very small time difference (within tolerance)."""
        mtime = Path(self.test_file).stat().st_mtime
        # Add tiny difference within tolerance
        slightly_different = mtime + 0.0001
        self.assertTrue(is_cache_valid(self.test_file, slightly_different))


class TestEnsureOutputDir(unittest.TestCase):
    """Tests for _ensure_output_dir function."""
    
    def setUp(self):
        """Set up test directory."""
        self.temp_dir = tempfile.mkdtemp()
    
    def tearDown(self):
        """Clean up temporary directory."""
        # Note: _ensure_output_dir creates directories in PACKAGE_DIR, not temp_dir
        # So we only clean up our temp_dir here
        if Path(self.temp_dir).exists():
            Path(self.temp_dir).rmdir()
    
    def test_ensure_output_dir_creates_directory(self):
        """Test that _ensure_output_dir creates directory if it doesn't exist."""
        # This will create a directory in the package dir, which is fine for testing
        result = _ensure_output_dir("test_output_dir_temp")
        self.assertTrue(result.exists())
        self.assertTrue(result.is_dir())
        # Clean up
        if result.exists():
            result.rmdir()
    
    def test_ensure_output_dir_existing_directory(self):
        """Test that _ensure_output_dir works with existing directory."""
        # Create directory first
        result1 = _ensure_output_dir("test_output_dir_temp2")
        # Call again
        result2 = _ensure_output_dir("test_output_dir_temp2")
        self.assertEqual(result1, result2)
        self.assertTrue(result2.exists())
        # Clean up
        if result2.exists():
            result2.rmdir()


class TestGenerateTimestampedFilename(unittest.TestCase):
    """Tests for _generate_timestamped_filename function."""
    
    def test_generate_timestamped_filename(self):
        """Test generating timestamped filename."""
        result = _generate_timestamped_filename("test_subdir", "test_prefix", "txt")
        
        # Check it's a Path object
        self.assertIsInstance(result, Path)
        
        # Check filename contains prefix
        self.assertIn("test_prefix", result.name)
        
        # Check extension
        self.assertTrue(result.name.endswith(".txt"))
        
        # Check parent directory ends with test_subdir
        self.assertTrue(result.parent.name == "test_subdir")
        
        # Clean up directory if it was created
        if result.parent.exists():
            result.parent.rmdir()
    
    def test_generate_timestamped_filename_unique(self):
        """Test that consecutive calls generate unique filenames."""
        result1 = _generate_timestamped_filename("test_subdir2", "prefix", "wav")
        time.sleep(0.001)  # Small delay to ensure different timestamp
        result2 = _generate_timestamped_filename("test_subdir2", "prefix", "wav")
        
        self.assertNotEqual(result1.name, result2.name)
        
        # Clean up
        if result1.parent.exists():
            result1.parent.rmdir()


class TestNote(unittest.TestCase):
    """Tests for Note dataclass."""
    
    def test_note_creation(self):
        """Test creating a Note object."""
        note = Note(start=0.0, end=1.0, pitch=60, velocity=100, channel=0)
        
        self.assertEqual(note.start, 0.0)
        self.assertEqual(note.end, 1.0)
        self.assertEqual(note.pitch, 60)
        self.assertEqual(note.velocity, 100)
        self.assertEqual(note.channel, 0)
    
    def test_note_default_values(self):
        """Test Note with default values."""
        note = Note(start=0.5, end=1.5, pitch=64)
        
        self.assertEqual(note.start, 0.5)
        self.assertEqual(note.end, 1.5)
        self.assertEqual(note.pitch, 64)
        self.assertEqual(note.velocity, 100)  # Default
        self.assertEqual(note.channel, 0)  # Default
    
    def test_note_equality(self):
        """Test Note equality comparison."""
        note1 = Note(start=0.0, end=1.0, pitch=60, velocity=100, channel=0)
        note2 = Note(start=0.0, end=1.0, pitch=60, velocity=100, channel=0)
        note3 = Note(start=0.0, end=1.0, pitch=61, velocity=100, channel=0)
        
        self.assertEqual(note1, note2)
        self.assertNotEqual(note1, note3)
