import wave
import threading
import queue
import time
import os
from datetime import datetime
from PyQt5.QtCore import QObject, pyqtSignal

# Try to import PyAudio, but handle if it's not available
try:
    import pyaudio
    PYAUDIO_AVAILABLE = True
except ImportError:
    PYAUDIO_AVAILABLE = False
    print("Warning: PyAudio not installed. Audio playback will be disabled.")
    print("To enable playback, install with: sudo dnf install python3-devel portaudio-devel && pip install pyaudio")

class AudioPlayer(QObject):
    playback_started = pyqtSignal(int)  # client_id
    playback_stopped = pyqtSignal(int)  # client_id
    recording_saved = pyqtSignal(int, str)  # client_id, filepath
    
    def __init__(self):
        super().__init__()
        self.audio = None
        self.streams = {}  # client_id -> stream
        self.buffers = {}  # client_id -> queue
        self.threads = {}  # client_id -> thread
        self.recording_buffers = {}  # client_id -> list of audio data
        self.recording_enabled = {}  # client_id -> bool
        self.playback_enabled = {}  # client_id -> bool
        self.sample_rate = 8000
        self.channels = 1
        self.chunk_size = 320  # 40ms at 8kHz
        self.debug_callback = None
        
        if PYAUDIO_AVAILABLE:
            try:
                self.audio = pyaudio.PyAudio()
            except Exception as e:
                self.debug(f"Failed to initialize PyAudio: {e}")
                self.audio = None
        else:
            self.audio = None
            self.debug("PyAudio not available - playback disabled, recording still works")
    
    def debug(self, message):
        if self.debug_callback:
            self.debug_callback(f"[AudioPlayer] {message}")
        else:
            print(f"[AudioPlayer] {message}")
    
    def set_debug_callback(self, callback):
        self.debug_callback = callback
    
    def start_playback(self, client_id):
        """Start audio playback for a client"""
        if not self.audio:
            self.debug("Audio playback not available - PyAudio not installed")
            self.debug("To enable: sudo dnf install python3-devel portaudio-devel && pip install pyaudio")
            return False
            
        if client_id in self.streams:
            self.debug(f"Playback already active for client {client_id}")
            return False
        
        try:
            # Create audio stream
            stream = self.audio.open(
                format=pyaudio.paInt16,
                channels=self.channels,
                rate=self.sample_rate,
                output=True,
                frames_per_buffer=self.chunk_size
            )
            
            self.streams[client_id] = stream
            self.buffers[client_id] = queue.Queue()
            self.playback_enabled[client_id] = True
            
            # Start playback thread
            thread = threading.Thread(
                target=self._playback_thread,
                args=(client_id,),
                daemon=True
            )
            self.threads[client_id] = thread
            thread.start()
            
            self.debug(f"Started playback for client {client_id}")
            self.playback_started.emit(client_id)
            return True
            
        except Exception as e:
            self.debug(f"Failed to start playback for client {client_id}: {e}")
            return False
    
    def stop_playback(self, client_id):
        """Stop audio playback for a client"""
        if client_id not in self.streams:
            return
        
        self.playback_enabled[client_id] = False
        
        # Wait for thread to finish
        if client_id in self.threads:
            self.threads[client_id].join(timeout=1.0)
            del self.threads[client_id]
        
        # Close stream
        if client_id in self.streams:
            try:
                self.streams[client_id].stop_stream()
                self.streams[client_id].close()
            except:
                pass
            del self.streams[client_id]
        
        # Clear buffer
        if client_id in self.buffers:
            del self.buffers[client_id]
        
        self.debug(f"Stopped playback for client {client_id}")
        self.playback_stopped.emit(client_id)
    
    def add_audio_data(self, client_id, pcm_data):
        """Add audio data to playback buffer"""
        # Initialize frame counter for debug logging
        if not hasattr(self, '_frame_count'):
            self._frame_count = {}
        if client_id not in self._frame_count:
            self._frame_count[client_id] = 0
        self._frame_count[client_id] += 1
        
        # Only log occasionally to avoid spam
        if self._frame_count[client_id] == 1 or self._frame_count[client_id] % 25 == 0:
            self.debug(f"Client {client_id} audio frame #{self._frame_count[client_id]}: {len(pcm_data)} bytes")
        
        if client_id in self.buffers:
            self.buffers[client_id].put(pcm_data)
            if self._frame_count[client_id] == 1:
                self.debug(f"Client {client_id} buffer started, queue size: {self.buffers[client_id].qsize()}")
        else:
            self.debug(f"Client {client_id} has no buffer (playback not started?)")
        
        # Add to recording buffer if recording
        if self.recording_enabled.get(client_id, False):
            if client_id not in self.recording_buffers:
                self.recording_buffers[client_id] = []
            self.recording_buffers[client_id].append(pcm_data)
    
    def _playback_thread(self, client_id):
        """Thread function for audio playback"""
        stream = self.streams.get(client_id)
        buffer = self.buffers.get(client_id)
        
        if not stream or not buffer:
            return
        
        self.debug(f"Playback thread started for client {client_id}")
        
        while self.playback_enabled.get(client_id, False):
            try:
                # Get audio data from buffer with timeout
                audio_data = buffer.get(timeout=0.1)
                
                # Only log first frame to avoid spam
                if not hasattr(self, '_playback_logged'):
                    self._playback_logged = {}
                if client_id not in self._playback_logged:
                    self._playback_logged[client_id] = False
                    
                if not self._playback_logged[client_id]:
                    self.debug(f"Client {client_id} playback thread playing first frame: {len(audio_data)} bytes")
                    self._playback_logged[client_id] = True
                
                # Play audio
                stream.write(audio_data)
                
            except queue.Empty:
                # No data available, continue
                continue
            except Exception as e:
                self.debug(f"Playback error for client {client_id}: {e}")
                break
        
        self.debug(f"Playback thread ended for client {client_id}")
    
    def start_recording(self, client_id):
        """Start recording received audio"""
        self.recording_enabled[client_id] = True
        self.recording_buffers[client_id] = []
        self.debug(f"Started recording for client {client_id}")
    
    def stop_recording(self, client_id, save_path=None):
        """Stop recording and optionally save to file"""
        if not self.recording_enabled.get(client_id, False):
            return None
        
        self.recording_enabled[client_id] = False
        
        if client_id not in self.recording_buffers:
            return None
        
        audio_data = self.recording_buffers[client_id]
        
        if not audio_data:
            self.debug(f"No audio data recorded for client {client_id}")
            return None
        
        # Generate filename if not provided
        if not save_path:
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            save_path = f"wav/received_client{client_id}_{timestamp}.wav"
        
        # Ensure directory exists
        save_dir = os.path.dirname(save_path)
        if save_dir:
            os.makedirs(save_dir, exist_ok=True)
        
        try:
            # Combine all audio chunks
            combined_audio = b''.join(audio_data)
            
            # Save as WAV file
            with wave.open(save_path, 'wb') as wav_file:
                wav_file.setnchannels(self.channels)
                wav_file.setsampwidth(2)  # 16-bit
                wav_file.setframerate(self.sample_rate)
                wav_file.writeframes(combined_audio)
            
            self.debug(f"Saved recording for client {client_id} to {save_path}")
            self.recording_saved.emit(client_id, save_path)
            
            # Clear recording buffer
            del self.recording_buffers[client_id]
            
            return save_path
            
        except Exception as e:
            self.debug(f"Failed to save recording for client {client_id}: {e}")
            return None
    
    def cleanup(self):
        """Clean up audio resources"""
        # Stop all playback
        for client_id in list(self.streams.keys()):
            self.stop_playback(client_id)
        
        # Terminate PyAudio
        if self.audio:
            self.audio.terminate()
            self.audio = None