import threading
import soundcard as sc
import azure.cognitiveservices.speech as speechsdk
import queue
import warnings
import numpy as np
from datetime import datetime
import streamlit as st
import time
import pandas as pd
from collections import deque
import os
import json
import logging

# Setup logging
os.makedirs("logs", exist_ok=True)
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler("logs/transcription_debug.log"),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger("teams_transcriber")

# Suppress soundcard warnings
warnings.filterwarnings("ignore", category=sc.SoundcardRuntimeWarning)

# Replace with your own Azure key
AZURE_KEY = 'A4oXBNqAo660Ylgs1wrmMUMuOU5plrgMnqi3CJ5zMBlQ9NwSP17TJQQJ99BBACYeBjFXJ3w3AAAYACOGsjvO'
AZURE_REGION = 'eastus'

class AudioMonitor:
    def __init__(self):
        self.mic_level = 0.0
        self.speaker_level = 0.0
        self.mic_silent_duration = 0.0
        self.speaker_silent_duration = 0.0
        self.is_running = True
        self.mic_queue = queue.Queue()
        self.speaker_queue = queue.Queue()
        self.mic_data_received = False
        self.speaker_data_received = False

    def update_levels(self, source, level):
        level = float(level)
        if source == "mic":
            self.mic_level = level
            if level > 0.005:  # Lower threshold to detect quieter sounds
                self.mic_data_received = True
                logger.info(f"Microphone level: {level:.4f}")
            if level < 0.005:
                self.mic_silent_duration += 0.1
            else:
                self.mic_silent_duration = 0
        else:
            self.speaker_level = level
            if level > 0.005:  # Lower threshold
                self.speaker_data_received = True
                logger.info(f"Speaker level: {level:.4f}")
            if level < 0.005:
                self.speaker_silent_duration += 0.1
            else:
                self.speaker_silent_duration = 0

class TranscriptionManager:
    def __init__(self, meeting_name):
        self.transcriptions = deque(maxlen=1000)
        self.is_running = True
        self.meeting_name = meeting_name
        self.timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        self.base_path = "transcriptions"
        self._init_files()

    def _init_files(self):
        # Create directory structure
        self.meeting_dir = os.path.join(self.base_path, f"{self.meeting_name}_{self.timestamp}")
        os.makedirs(self.meeting_dir, exist_ok=True)
        logger.info(f"Created transcription directory: {self.meeting_dir}")

        # Initialize files
        self.txt_path = os.path.join(self.meeting_dir, "transcription.txt")
        self.json_path = os.path.join(self.meeting_dir, "transcription.json")

        # Create initial files
        with open(self.txt_path, "w", encoding="utf-8") as f:
            f.write(f"=== Meeting: {self.meeting_name} ===\n")
            f.write(f"=== Started at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ===\n\n")
            logger.info(f"Initialized text file: {self.txt_path}")

        with open(self.json_path, "w", encoding="utf-8") as f:
            json.dump({"meeting_name": self.meeting_name, "start_time": self.timestamp, "transcriptions": []}, f, indent=2)
            logger.info(f"Initialized JSON file: {self.json_path}")

    def add_transcription(self, source, text):
        if not text.strip():
            return

        # Create transcription entry
        timestamp = datetime.now().strftime("%H:%M:%S")
        transcription = {
            'timestamp': timestamp,
            'source': source,
            'text': text
        }
        
        logger.info(f"New transcription from {source}: {text}")

        # Add to memory buffer
        self.transcriptions.appendleft(transcription)

        # Save to text file
        try:
            with open(self.txt_path, "a", encoding="utf-8") as f:
                f.write(f"[{timestamp}] {source}: {text}\n")
                f.flush()
                logger.debug(f"Saved to text file: {self.txt_path}")
        except Exception as e:
            error_msg = f"Error writing to text file: {str(e)}"
            logger.error(error_msg)
            st.error(error_msg)

        # Save to JSON file
        try:
            with open(self.json_path, "r+", encoding="utf-8") as f:
                data = json.load(f)
                data["transcriptions"].append(transcription)
                f.seek(0)
                json.dump(data, f, indent=2)
                f.truncate()
                logger.debug(f"Saved to JSON file: {self.json_path}")
        except Exception as e:
            error_msg = f"Error writing to JSON file: {str(e)}"
            logger.error(error_msg)
            st.error(error_msg)

def test_audio_devices():
    """Test function to check audio devices"""
    logger.info("Testing audio devices...")
    result = {
        "mic_name": None,
        "speaker_name": None,
        "mic_level": 0,
        "speaker_level": 0,
        "microphones": [],
        "speakers": []
    }
    
    try:
        # Get all audio devices
        all_mics = sc.all_microphones()
        all_speakers = sc.all_speakers()
        
        # Get default devices
        default_mic = sc.default_microphone()
        default_speaker = sc.default_speaker()
        
        result["mic_name"] = default_mic.name if default_mic else "None"
        result["speaker_name"] = default_speaker.name if default_speaker else "None"
        result["microphones"] = [mic.name for mic in all_mics]
        result["speakers"] = [speaker.name for speaker in all_speakers]
        
        logger.info(f"Default microphone: {result['mic_name']}")
        logger.info(f"Default speaker: {result['speaker_name']}")
        logger.info(f"Found {len(all_mics)} microphones and {len(all_speakers)} speakers")
        
        # Test microphone recording
        if default_mic:
            logger.info("Testing microphone recording (3 seconds)...")
            with default_mic.recorder(samplerate=16000) as mic_recorder:
                data = mic_recorder.record(numframes=16000 * 3)
                level = float(np.abs(data).mean())
                result["mic_level"] = level
                logger.info(f"Microphone level: {level:.6f}")
        
        # Test speaker capture with loopback
        if default_speaker:
            logger.info("Testing speaker with loopback (3 seconds)...")
            try:
                speaker = sc.get_microphone(id=str(default_speaker.name), include_loopback=True)
                with speaker.recorder(samplerate=16000) as speaker_recorder:
                    data = speaker_recorder.record(numframes=16000 * 3)
                    level = float(np.abs(data).mean())
                    result["speaker_level"] = level
                    logger.info(f"Speaker level: {level:.6f}")
            except Exception as e:
                logger.error(f"Speaker loopback test error: {e}")
        
    except Exception as e:
        logger.error(f"Audio device test error: {e}")
    
    logger.info("Audio device test completed")
    return result

def test_azure_speech():
    """Test Azure Speech Recognition"""
    logger.info("Testing Azure Speech Recognition...")
    result = {
        "success": False,
        "text": "",
        "error": ""
    }
    
    try:
        # Create speech configuration
        speech_config = speechsdk.SpeechConfig(subscription=AZURE_KEY, region=AZURE_REGION)
        speech_config.speech_recognition_language = "en-US"
        
        # Create audio configuration using default microphone
        audio_config = speechsdk.audio.AudioConfig(use_default_microphone=True)
        
        # Create recognizer
        speech_recognizer = speechsdk.SpeechRecognizer(speech_config=speech_config, audio_config=audio_config)
        
        logger.info("Speak into your microphone...")
        
        # Start speech recognition
        result_future = speech_recognizer.recognize_once_async()
        
        def on_recognized(evt):
            logger.info(f"Recognition result: {evt}")
        
        speech_recognizer.recognized.connect(on_recognized)
        
        # Wait for the result
        recognition_result = result_future.get()
        
        # Check result
        if recognition_result.reason == speechsdk.ResultReason.RecognizedSpeech:
            result["text"] = recognition_result.text
            result["success"] = True
            logger.info(f"Azure test - Recognized: {recognition_result.text}")
        elif recognition_result.reason == speechsdk.ResultReason.NoMatch:
            result["error"] = f"No speech could be recognized: {recognition_result.no_match_details}"
            logger.warning(f"Azure test - No match: {recognition_result.no_match_details}")
        elif recognition_result.reason == speechsdk.ResultReason.Canceled:
            cancellation = recognition_result.cancellation_details
            result["error"] = f"Speech Recognition canceled: {cancellation.reason}"
            logger.error(f"Azure test - Canceled: {cancellation.reason}")
            if cancellation.reason == speechsdk.CancellationReason.Error:
                result["error"] += f" Error details: {cancellation.error_details}"
                logger.error(f"Azure test - Error details: {cancellation.error_details}")
    
    except Exception as e:
        result["error"] = str(e)
        logger.error(f"Azure speech test error: {e}")
    
    logger.info("Azure speech test completed")
    return result

def capture_mic_audio(audio_monitor):
    logger.info("Starting microphone capture thread")
    try:
        mic = sc.get_microphone(id=str(sc.default_microphone().name))
        logger.info(f"Microphone initialized: {mic.name}")
        
        with mic.recorder(samplerate=16000) as mic_recorder:
            logger.info("Microphone recorder started")
            sample_count = 0
            
            while audio_monitor.is_running:
                try:
                    data = mic_recorder.record(numframes=1024)
                    sample_count += 1
                    
                    # Log periodic stats
                    if sample_count % 100 == 0:
                        logger.debug(f"Mic samples processed: {sample_count}, queue size: {audio_monitor.mic_queue.qsize()}")
                    
                    level = float(np.abs(data).mean())
                    audio_monitor.update_levels("mic", level)
                    
                    if len(data.shape) > 1:
                        data = np.mean(data, axis=1)
                    data = (data * 32767).astype(np.int16)
                    audio_monitor.mic_queue.put(data.tobytes())
                except Exception as e:
                    logger.error(f"Microphone recording error: {str(e)}")
                    time.sleep(1)
    except Exception as e:
        logger.error(f"Microphone initialization error: {str(e)}")
    
    logger.info("Microphone capture thread stopped")

def capture_speaker_audio(audio_monitor):
    logger.info("Starting speaker capture thread")
    try:
        speaker = sc.get_microphone(id=str(sc.default_speaker().name), include_loopback=True)
        logger.info(f"Speaker initialized: {speaker.name}")
        
        with speaker.recorder(samplerate=16000) as speaker_recorder:
            logger.info("Speaker recorder started")
            sample_count = 0
            
            while audio_monitor.is_running:
                try:
                    data = speaker_recorder.record(numframes=1024)
                    sample_count += 1
                    
                    # Log periodic stats
                    if sample_count % 100 == 0:
                        logger.debug(f"Speaker samples processed: {sample_count}, queue size: {audio_monitor.speaker_queue.qsize()}")
                    
                    level = float(np.abs(data).mean())
                    audio_monitor.update_levels("speaker", level)
                    
                    if len(data.shape) > 1:
                        data = np.mean(data, axis=1)
                    data = (data * 32767).astype(np.int16)
                    audio_monitor.speaker_queue.put(data.tobytes())
                except Exception as e:
                    logger.error(f"Speaker recording error: {str(e)}")
                    time.sleep(1)
    except Exception as e:
        logger.error(f"Speaker initialization error: {str(e)}")
    
    logger.info("Speaker capture thread stopped")

def create_pull_audio_stream(audio_queue):
    class PullAudioStream(speechsdk.audio.PullAudioInputStreamCallback):
        def __init__(self):
            super().__init__()
            self._buffer = bytes()
            self.bytes_read = 0

        def read(self, buffer: memoryview) -> int:
            try:
                if not self._buffer:
                    try:
                        self._buffer = audio_queue.get(timeout=0.1)
                    except queue.Empty:
                        return 0
                
                bytes_to_copy = min(len(buffer), len(self._buffer))
                buffer[:bytes_to_copy] = self._buffer[:bytes_to_copy]
                self._buffer = self._buffer[bytes_to_copy:]
                self.bytes_read += bytes_to_copy
                
                # Log bytes read every 100KB
                if self.bytes_read % 102400 < bytes_to_copy:
                    logger.debug(f"Audio stream bytes read: {self.bytes_read}")
                
                return bytes_to_copy
            except Exception as e:
                logger.error(f"Error in audio stream read: {e}")
                return 0

        def close(self):
            logger.info(f"Audio stream closed. Total bytes read: {self.bytes_read}")
            pass

    return speechsdk.audio.AudioConfig(stream=speechsdk.audio.PullAudioInputStream(PullAudioStream()))

def setup_recognizer(audio_config, name, transcription_manager):
    logger.info(f"Setting up {name} recognizer")
    speech_config = speechsdk.SpeechConfig(subscription=AZURE_KEY, region=AZURE_REGION)
    speech_config.speech_recognition_language = "en-US"
    speech_config.set_property(speechsdk.PropertyId.Speech_LogFilename, f"logs/speech_log_{name}.txt")
    
    # Enable more detailed recognition for better results
    speech_config.enable_audio_logging()
    speech_config.set_property(speechsdk.PropertyId.SpeechServiceConnection_InitialSilenceTimeoutMs, "5000")
    speech_config.set_property(speechsdk.PropertyId.SpeechServiceConnection_EndSilenceTimeoutMs, "5000")
    
    recognizer = speechsdk.SpeechRecognizer(speech_config=speech_config, audio_config=audio_config)
    
    def handle_recognizing(evt):
        # Log partial/interim results
        if evt.result.text:
            logger.debug(f"Recognizing ({name}): {evt.result.text}")
    
    def handle_recognized(evt):
        if evt.result.reason == speechsdk.ResultReason.RecognizedSpeech:
            text = evt.result.text.strip()
            if text:
                logger.info(f"Recognized ({name}): {text}")
                transcription_manager.add_transcription(name, text)
        elif evt.result.reason != speechsdk.ResultReason.NoMatch:
            logger.debug(f"Recognition event with reason: {evt.result.reason}")
    
    def handle_canceled(evt):
        if evt.result.reason == speechsdk.ResultReason.Canceled:
            details = evt.result.cancellation_details
            logger.warning(f"Recognition canceled ({name}): {details.reason}")
            if details.reason == speechsdk.CancellationReason.Error:
                logger.error(f"Error details: {details.error_details}")
    
    def handle_session_started(evt):
        logger.info(f"Speech recognition session started ({name}): {evt}")
    
    def handle_session_stopped(evt):
        logger.info(f"Speech recognition session stopped ({name}): {evt}")
    
    # Connect event handlers
    recognizer.recognizing.connect(handle_recognizing)
    recognizer.recognized.connect(handle_recognized)
    recognizer.canceled.connect(handle_canceled)
    recognizer.session_started.connect(handle_session_started)
    recognizer.session_stopped.connect(handle_session_stopped)
    
    logger.info(f"Starting continuous recognition for {name}")
    future = recognizer.start_continuous_recognition_async()
    try:
        future.get()
        logger.info(f"Continuous recognition started for {name}")
    except Exception as e:
        logger.error(f"Failed to start continuous recognition for {name}: {e}")
    
    return recognizer

def main():
    st.set_page_config(page_title="Teams Call Transcription", layout="wide")
    st.title("Teams Call Transcription System")
    
    # Meeting name input
    meeting_name = st.text_input("Enter Meeting Name", "Teams Meeting")
    
    # Initialize session state
    if 'running' not in st.session_state:
        st.session_state.running = False
        st.session_state.components = None
        st.session_state.debug_log = []
    
    # Debug section in sidebar
    st.sidebar.title("Debug Tools")
    
    if st.sidebar.button("Test Audio Devices"):
        with st.sidebar.status("Testing audio devices...", expanded=True) as status:
            result = test_audio_devices()
            status.update(label="Test complete!", state="complete", expanded=True)
            st.sidebar.write(f"Default mic: {result['mic_name']}")
            st.sidebar.write(f"Default speaker: {result['speaker_name']}")
            st.sidebar.write(f"Mic level: {result['mic_level']:.6f}")
            st.sidebar.write(f"Speaker level: {result['speaker_level']:.6f}")
            st.sidebar.write(f"Found {len(result['microphones'])} microphones and {len(result['speakers'])} speakers")
            
            # Show all devices
            with st.sidebar.expander("All microphones"):
                for i, mic in enumerate(result['microphones']):
                    st.write(f"{i+1}. {mic}")
            
            with st.sidebar.expander("All speakers"):
                for i, speaker in enumerate(result['speakers']):
                    st.write(f"{i+1}. {speaker}")
    
    if st.sidebar.button("Test Azure Speech"):
        with st.sidebar.status("Testing Azure Speech (speak into your microphone)...", expanded=True) as status:
            result = test_azure_speech()
            if result["success"]:
                status.update(label="Test complete!", state="complete", expanded=True)
                st.sidebar.success(f"Recognized: '{result['text']}'")
            else:
                status.update(label="Test failed!", state="error", expanded=True)
                st.sidebar.error(f"Error: {result['error']}")
    
    # View logs option
    if st.sidebar.button("View Debug Logs"):
        try:
            with open("logs/transcription_debug.log", "r") as f:
                logs = f.readlines()
                st.session_state.debug_log = logs[-50:]  # Show last 50 lines
        except Exception as e:
            st.session_state.debug_log = [f"Error reading log file: {e}"]
    
    if st.session_state.debug_log:
        with st.sidebar.expander("Recent Logs", expanded=True):
            for line in st.session_state.debug_log:
                st.write(line)
    
    # Main UI
    col1, col2 = st.columns([1, 5])
    with col1:
        if st.button("Start" if not st.session_state.running else "Stop"):
            st.session_state.running = not st.session_state.running
            
            if st.session_state.running:
                try:
                    logger.info(f"Starting new transcription session: {meeting_name}")
                    
                    # Initialize components
                    audio_monitor = AudioMonitor()
                    transcription_manager = TranscriptionManager(meeting_name)
                    
                    # Start audio capture threads
                    mic_thread = threading.Thread(target=capture_mic_audio, 
                                              args=(audio_monitor,), 
                                              daemon=True)
                    mic_thread.start()
                    logger.info("Microphone thread started")
                    
                    speaker_thread = threading.Thread(target=capture_speaker_audio, 
                                              args=(audio_monitor,), 
                                              daemon=True)
                    speaker_thread.start()
                    logger.info("Speaker thread started")
                    
                    # Give threads a moment to initialize and start collecting data
                    time.sleep(2)
                    
                    # Setup recognizers
                    logger.info("Creating audio configurations")
                    mic_audio_config = create_pull_audio_stream(audio_monitor.mic_queue)
                    speaker_audio_config = create_pull_audio_stream(audio_monitor.speaker_queue)
                    
                    logger.info("Setting up speech recognizers")
                    mic_recognizer = setup_recognizer(mic_audio_config, "Microphone", transcription_manager)
                    speaker_recognizer = setup_recognizer(speaker_audio_config, "Speaker", transcription_manager)
                    
                    # Store components in session state
                    st.session_state.components = {
                        'audio_monitor': audio_monitor,
                        'transcription_manager': transcription_manager,
                        'mic_recognizer': mic_recognizer,
                        'speaker_recognizer': speaker_recognizer
                    }
                    
                    # Show file paths
                    st.sidebar.success(f"Transcription files will be saved to:\n{transcription_manager.meeting_dir}")
                    logger.info(f"Transcription system started successfully")
                    
                except Exception as e:
                    error_msg = f"Error starting transcription: {str(e)}"
                    logger.error(error_msg)
                    st.error(error_msg)
                    st.session_state.running = False
            
            else:
                logger.info("Stopping transcription system")
                # Stop everything
                if st.session_state.components:
                    st.session_state.components['audio_monitor'].is_running = False
                    try:
                        st.session_state.components['mic_recognizer'].stop_continuous_recognition_async()
                        st.session_state.components['speaker_recognizer'].stop_continuous_recognition_async()
                        logger.info("Recognizers stopped")
                    except Exception as e:
                        logger.error(f"Error stopping recognizers: {e}")
                    
                    st.session_state.components = None
                    logger.info("Transcription system stopped")
    
    # Display audio levels and transcriptions when running
    if st.session_state.running and st.session_state.components:
        components = st.session_state.components
        
        # Audio levels
        col1, col2 = st.columns(2)
        with col1:
            st.subheader("Microphone")
            mic_level = st.progress(0)
            mic_status = st.empty()
            mic_data_status = st.empty()
        
        with col2:
            st.subheader("Speaker")
            speaker_level = st.progress(0)
            speaker_status = st.empty()
            speaker_data_status = st.empty()
        
        # Transcription display
        st.subheader("Live Transcription")
        transcription_area = st.empty()
        
        # Status messages
        status_area = st.empty()
        
        # Update loop
        update_count = 0
        while st.session_state.running:
            try:
                update_count += 1
                # Update audio levels
                mic_level.progress(float(min(1.0, components['audio_monitor'].mic_level * 20)))  # More sensitive
                speaker_level.progress(float(min(1.0, components['audio_monitor'].speaker_level * 20)))
                
                mic_status.text("Status: " + ("Active" if components['audio_monitor'].mic_silent_duration < 5 else "No voice detected"))
                speaker_status.text("Status: " + ("Active" if components['audio_monitor'].speaker_silent_duration < 5 else "No audio detected"))
                
                # Show if data has been received
                mic_data_status.text("Data received: " + ("Yes" if components['audio_monitor'].mic_data_received else "No"))
                speaker_data_status.text("Data received: " + ("Yes" if components['audio_monitor'].speaker_data_received else "No"))
                
                # Update transcriptions
                if components['transcription_manager'].transcriptions:
                    df = pd.DataFrame(list(components['transcription_manager'].transcriptions))
                    transcription_area.dataframe(df, hide_index=True)
                else:
                    # If no transcriptions yet, show helpful status message
                    if update_count % 10 == 0:  # Update every ~1 second
                        elapsed = update_count // 10
                        if elapsed < 10:
                            status_area.info(f"Waiting for audio... ({elapsed}s)")
                        elif elapsed < 30:
                            status_area.info(f"Listening for speech... ({elapsed}s)")
                        else:
                            status_area.warning(f"No transcriptions yet. Try speaking louder or check debug tools. ({elapsed}s)")
                            
                            # Log potential issues periodically
                            if elapsed % 30 == 0:  # Every 30 seconds
                                logger.warning(f"No transcriptions after {elapsed} seconds")
                                mic_queue_size = components['audio_monitor'].mic_queue.qsize()
                                speaker_queue_size = components['audio_monitor'].speaker_queue.qsize()
                                logger.info(f"Queue sizes - Mic: {mic_queue_size}, Speaker: {speaker_queue_size}")
                                logger.info(f"Audio levels - Mic: {components['audio_monitor'].mic_level:.6f}, Speaker: {components['audio_monitor'].speaker_level:.6f}")
                
                time.sleep(0.1)
            except Exception as e:
                logger.error(f"Error in update loop: {e}")
                time.sleep(1)
    
    else:
        st.info("Enter a meeting name and click 'Start' to begin transcription")

if __name__ == "__main__":
    main()    this code is not wrting in the txt file ..means the azure speech text is recognising the mic ...whatevr i speak but in test audio device 2025-02-25 10:44:54,024 [ERROR] Audio device test 
Default mic: None
Default speaker: None
Mic level: 0.000000
Speaker level: 0.000000
Found 0 microphones and 0 speakers  getting this in audio test .... Test complete!
Recognized: 'Hello, my name is <PRESIDIO_ANONYMIZED_PERSON>.'  this is axure tst result  getting this ..make chnges to take a note of mic and spaeker audio