#!/usr/bin/env python3
"""
Test Suite for Dictation Service
Tests dictation functionality and system tray integration
"""

import os
import sys
import unittest
import tempfile
from unittest.mock import Mock, patch, MagicMock

# Mock GTK modules before importing
sys.modules['gi'] = MagicMock()
sys.modules['gi.repository'] = MagicMock()
sys.modules['gi.repository.Gtk'] = MagicMock()
sys.modules['gi.repository.AppIndicator3'] = MagicMock()
sys.modules['gi.repository.GLib'] = MagicMock()

# Add src to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))


class TestDictationCore(unittest.TestCase):
    """Test core dictation functionality"""

    def setUp(self):
        """Setup test environment"""
        self.temp_dir = tempfile.mkdtemp()
        self.lock_file = os.path.join(self.temp_dir, "test_listening.lock")

    def tearDown(self):
        """Clean up test environment"""
        if os.path.exists(self.lock_file):
            os.remove(self.lock_file)
        try:
            os.rmdir(self.temp_dir)
        except:
            pass

    def test_can_import_dictation_service(self):
        """Test that main service can be imported"""
        try:
            from dictation_service import ai_dictation_simple
            self.assertTrue(hasattr(ai_dictation_simple, 'main'))
            self.assertTrue(hasattr(ai_dictation_simple, 'DictationTrayIcon'))
        except ImportError as e:
            self.fail(f"Cannot import dictation service: {e}")

    def test_spurious_word_filtering(self):
        """Test that spurious words are filtered"""
        from dictation_service.ai_dictation_simple import process_final_text

        # Mock subprocess.run to avoid actual typing
        with patch('subprocess.run'):
            # Single spurious word should be filtered
            process_final_text("the")  # Should be filtered (single word)
            process_final_text("a")     # Should be filtered

            # Multi-word with spurious words should have them removed
            # This is hard to test without capturing output, so just ensure no crash
            process_final_text("the hello world the")

    def test_lock_file_detection(self):
        """Test lock file creation and detection"""
        # Create lock file
        with open(self.lock_file, 'w') as f:
            f.write("")

        self.assertTrue(os.path.exists(self.lock_file))

        # Remove lock file
        os.remove(self.lock_file)
        self.assertFalse(os.path.exists(self.lock_file))

    @patch('subprocess.check_call')
    @patch('os.path.exists')
    def test_model_download(self, mock_exists, mock_check_call):
        """Test Vosk model download logic"""
        from dictation_service.ai_dictation_simple import download_model_if_needed

        # Mock model already exists
        mock_exists.return_value = True
        download_model_if_needed()
        mock_check_call.assert_not_called()


class TestSystemTrayIcon(unittest.TestCase):
    """Test system tray icon functionality"""

    @patch('gi.repository.AppIndicator3.Indicator')
    @patch('gi.repository.Gtk.Menu')
    def test_tray_icon_creation(self, mock_menu, mock_indicator):
        """Test that tray icon can be created"""
        from dictation_service.ai_dictation_simple import DictationTrayIcon

        # This may fail if GTK is not available, which is okay
        try:
            tray = DictationTrayIcon()
            self.assertIsNotNone(tray)
        except Exception as e:
            # GTK not available in test environment is acceptable
            self.skipTest(f"GTK not available: {e}")

    def test_tray_toggle_creates_lock_file(self):
        """Test that tray icon toggle creates/removes lock file"""
        temp_lock = tempfile.mktemp(suffix='.lock')

        try:
            # Simulate creating lock file
            with open(temp_lock, 'w') as f:
                pass
            self.assertTrue(os.path.exists(temp_lock))

            # Simulate removing lock file
            os.remove(temp_lock)
            self.assertFalse(os.path.exists(temp_lock))
        finally:
            if os.path.exists(temp_lock):
                os.remove(temp_lock)


class TestAudioProcessing(unittest.TestCase):
    """Test audio processing functionality"""

    def test_audio_callback_ignores_tts_lock(self):
        """Test that audio callback respects TTS lock file"""
        from dictation_service.ai_dictation_simple import audio_callback

        lock_file = "/tmp/dictation_speaking.lock"

        try:
            # Create TTS lock file
            with open(lock_file, 'w') as f:
                f.write("test")

            # Audio callback should ignore input when lock exists
            # This is hard to test without actual audio, so just ensure no crash
            mock_data = b'\x00' * 4000
            audio_callback(mock_data, 4000, None, None)

        finally:
            if os.path.exists(lock_file):
                os.remove(lock_file)

    @patch('vosk.Model')
    @patch('vosk.KaldiRecognizer')
    def test_recognizer_initialization(self, mock_recognizer, mock_model):
        """Test that Vosk recognizer can be initialized"""
        # This tests the mocking setup, actual initialization requires model files
        mock_model.return_value = MagicMock()
        mock_recognizer.return_value = MagicMock()

        # Just ensure mocks work
        self.assertIsNotNone(mock_model)
        self.assertIsNotNone(mock_recognizer)


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