import datetime
import json
import os
import uuid
import shutil
import aiofiles
import numpy as np
from fastapi import HTTPException, UploadFile

from pipeline.asr_processing import asr_instance
from pipeline.audio_processing import AudioProcessing, process_instance
from pipeline.diarization_processing import diarization_instance
from pipeline.logger import logger

diarized_folder = f"diarized_{uuid.uuid4()}"
output_folder = f"output_{uuid.uuid4()}"
enhanced_folder = f"enhanced_{uuid.uuid4()}"


class SpeechExecutedFunctions:

    # def __init__(self): 
    #     self.output_path = output_folder
    #     self.enhanced_path_dir = os.path.join(output_folder, enhanced_folder)

            # Language detecion
    def detect_language(self, detect_lang: bool, temp_file_path: str, default_lang : str):
        try:
            if detect_lang is True:
                predicted_lang = process_instance.lang_detection(temp_file_path, default_lang)
                default_langs = {"nl": "nl-NL", "en": "en-US"}  # FIXME refactor to emun
                predicted_lang = default_langs.get(predicted_lang, default_lang)
            else:
                predicted_lang = default_lang
        except Exception as e:
            logger.error(f"An error occurred during language detection: {e}")
            raise HTTPException(status_code=500, detail=f"Could not detect language: {str(e)}") from None
        
        return predicted_lang
    
    def speech_processing(self, noise_remover: bool, speech_enhancement: bool, temp_file_path : str, enhanced_output_path: str):
        # Using noise reduction tool to enhance audio quality
        if noise_remover:
            logger.info(" Noise removing is being used to improve poor quality of the input audio.")
            enhanced_audio_path = process_instance.audoai_noise_remover(
                temp_file_path, enhanced_output_path
            )
            asr_instance.blob_storage(enhanced_audio_path)

        # Using speech enhancement tool to enhance audio quality
        if speech_enhancement:
            logger.info(" Speech enhancing is being used to improve poor quality of the input audio.")
            enhanced_audio_path = process_instance.elevenlabs_speech_enhancer(
                temp_file_path, enhanced_output_path
            )
            asr_instance.blob_storage(enhanced_audio_path)

    def split_audio_and_save_chunks(self,
            audio_path: str,
            process_instance: AudioProcessing,
            enhanced_dir: str,
            original_filename: str,
            noise_remover: bool = False,
            speech_enhancement: bool = False,
            max_chunk_length: int = 60000,
            min_pause_length: int = 1000,
        ) -> tuple[list[str], str]:

            audio_chunks = process_instance.split_audio_on_silence(
                audio_path=audio_path, min_silence_len=2000, silence_thresh=None, keep_silence=250
            )
            logger.info(f"Initial number of chunks: {len(audio_chunks)}")

            final_chunk_paths: list[str] = []

            for i, chunk in enumerate(audio_chunks):
                chunk_length_ms = len(chunk)

                # If chunk exceeds 60 seconds, split it
                if chunk_length_ms > max_chunk_length:
                    logger.info(
                        f"Chunk {i} is longer than {max_chunk_length / 1000} seconds. Splitting further."
                    )

                    # Split the chunk based on smaller pauses (1 second or 0.5 second)
                    sub_chunks = process_instance.split_audio_on_silence(
                        chunk, min_pause_length, None, 250
                    )  # FIXME
                    logger.info(f"Chunk {i} split into {len(sub_chunks)} sub-chunks.")

                    for j, sub_chunk in enumerate(sub_chunks):
                        chunk_path = os.path.join(enhanced_dir, f"chunk_{i}_{j}.wav")
                        sub_chunk.export(chunk_path, format="wav")
                        final_chunk_paths.append(chunk_path)
                else:
                    # If the chunk is within the limit, save it directly
                    chunk_path = os.path.join(enhanced_dir, f"chunk_{i}.wav")
                    chunk.export(chunk_path, format="wav")
                    final_chunk_paths.append(chunk_path)

            os.remove(audio_path)
            return_filename = (
                f"enhanced_{original_filename}"
                if (noise_remover or speech_enhancement)
                else original_filename
            )
    
            return final_chunk_paths, return_filename
    
    def diarization_processing(self, diarize_flag: bool, vet_id: str, enhanced_audio_path: str, original_filename: str, noise_remover:bool, speech_enhancement: bool, predicted_lang: str):
        
        if diarize_flag:
                file_in_blob = vet_id + ".json"
                local_file = diarization_instance.search_blob(file_in_blob)
                logger.info(f"VetId: {vet_id}, local_file: {local_file}")

                if local_file is None:  # if vet-id doesn't exist
                    logger.info(
                        "Embeddings file does not exist or input name is invalid. Proceeding without vet's config file."
                    )

                    chunk_paths, filename = executeFunc.split_audio_and_save_chunks(
                        enhanced_audio_path,
                        process_instance,
                        exectServ.enhanced_path_dir,
                        original_filename,
                        noise_remover,
                        speech_enhancement,
                    )
                    return chunk_paths, filename, None, predicted_lang

                elif os.path.isfile(local_file):  # if vet-id exist
                    with open(local_file, "r") as f:
                        embeddings = json.load(f)
                        embeddings_array = np.array(embeddings)
                        os.remove(local_file)

                    # Diarization processing (distinguishing between vet and owner voices)
                    try:
        
                        chunk_paths, speakers = diarization_instance.diarization(
                            enhanced_audio_path, embeddings_array, diarized_folder
                        )
                        filename = (
                            f"enhanced_{original_filename}"
                            if (noise_remover or speech_enhancement)
                            else original_filename
                        )
                        return chunk_paths, filename, speakers, predicted_lang

                    except Exception as e:
                        logger.error(f"Diarization couldn't be completed: {e}")
                        raise HTTPException(
                            status_code=400, detail=f"Diarization couldn't be completed: {str(e)}"
                        ) from None
        else:
            # If diarize_flag is False, process audio normally without diarization
            chunk_paths, filename = executeFunc.split_audio_and_save_chunks(
                enhanced_audio_path,
                process_instance,
                exectServ.enhanced_path_dir,
                original_filename,
                noise_remover,
                speech_enhancement,
            )
            return chunk_paths, filename, None, predicted_lang
        
    async def recognition_process(self, chunk_paths: list, engine: str, predicted_lang: str, diarize_flag: bool, speakers: list)-> list:
        transcripts = []
        for i, chunk_path in enumerate(chunk_paths):
            try:
                # Google ASR for English and Dutch / Resample to 24kHz for Google Telephony
                if engine == "google":
                    process_instance.resample_audio(chunk_path, 24000)
                    phrase_set_id = (
                        "en-vet-words-domain" if predicted_lang == "en-US" else "nl-vet-words-domain"
                    )
                    transcript = await asr_instance.asr_google_word_boosting(
                        audio_uri=chunk_path, phrase_set_id=phrase_set_id, lang=predicted_lang
                    )

                # Azure ASR / Resample to 16kHz for Azure
                elif engine == "azure":
                    process_instance.resample_audio(chunk_path, 16000)
                    transcript = await asr_instance.asr_azure_continuous_recognition(
                        chunk_path, predicted_lang
                    )
                else:
                    raise HTTPException(
                        status_code=403,
                        detail="Invalid ASR engine or language. Supported: 'google', 'azure' with 'en-US', 'nl-NL'.",
                    )

                if diarize_flag and speakers:
                    speaker = speakers[i] if i < len(speakers) else "Unknown"
                    transcripts.append(f"({speaker}): {transcript}")
                else:
                    transcripts.append(transcript)

            except HTTPException as e:
                raise e
            except Exception as e:
                logger.error(f"Error transcribing {chunk_path}: {e}")
                raise HTTPException(
                    status_code=400, detail=f"Error transcribing {chunk_path}: {str(e)}"
                ) from None
            finally:
                os.remove(chunk_path)
        return " ".join(str(item) for item in transcripts)


class SpeechRecognitionService:

    async def init_and_save_files(self, file: UploadFile, original_filename : str):

        # executeFunc.__init__()
        self.output_path = output_folder
        self.enhanced_path_dir = os.path.join(output_folder, enhanced_folder)
        
        temp_file_path = os.path.join(self.output_path,  "temp_" + original_filename)
        os.makedirs(self.output_path, exist_ok=True)
        os.makedirs(self.enhanced_path_dir, exist_ok=True)

        # Check if the file is an mp3 by examining the content type 
        if file.content_type == "audio/mpeg":
            logger.error("Uploaded file is not an WAV file.")
            raise HTTPException(status_code=422, detail="Invalid file format. Please upload an WAV file.")

        async with aiofiles.open(temp_file_path, "wb") as temp_file:
            content = await file.read()
            await temp_file.write(content)

        return temp_file_path

    def process_and_enhance_file(
        self,
        temp_file_path: str,
        noise_remover: bool,
        speech_enhancement: bool,
        vet_id: str,
        default_lang: str,
        detect_lang: bool,
        diarize_flag: bool,
        original_filename: str,
    ):
        try:
            predicted_lang = executeFunc.detect_language(detect_lang, temp_file_path, default_lang)

            # Reassigning the audio file path variable
            enhanced_audio_path = temp_file_path
            enhanced_output_path = os.path.join(self.output_path, "enhanced_" + original_filename)

            # Using noise reduction tool to enhance audio quality
            executeFunc.speech_processing(noise_remover, speech_enhancement, temp_file_path, enhanced_output_path)
            chunk_paths, filename, _ , predicted_lang = executeFunc.diarization_processing(diarize_flag, vet_id, enhanced_audio_path, original_filename, noise_remover, speech_enhancement, predicted_lang)

            return chunk_paths, filename, _ , predicted_lang
        
        except Exception as e:
            logger.error(f"An error occurred during processing: {e}")
            raise HTTPException(status_code=500, detail=f"Error processing audio: {str(e)}") from None

    async def transcribe_chunks(
        self,
        chunk_paths: str,
        engine: str,
        diarize_flag: bool,
        speakers: list[str] | None,
        predicted_lang: str,
    ):

        transcripts = executeFunc.recognition_process(chunk_paths, engine, predicted_lang, diarize_flag, speakers)
        return transcripts
    

class RunService:

    async def recognize_speech(
        self,
        file: UploadFile,
        engine: str,
        default_lang: str,
        detect_lang: bool,
        vet_id: str,
        diarize_flag: bool,
        noise_remover: bool,
        speech_enhancement: bool,
        ):
        

        start_time = datetime.datetime.now()

        original_filename: str = file.filename  # type: ignore
        temp_file_path = await exectServ.init_and_save_files(file, original_filename)
        chunk_paths, processed_filename, speakers, predicted_lang = exectServ.process_and_enhance_file(
            temp_file_path=temp_file_path,
            noise_remover=noise_remover,
            speech_enhancement=speech_enhancement,
            vet_id=vet_id,
            default_lang=default_lang,
            detect_lang=detect_lang,
            diarize_flag=diarize_flag,
            original_filename=original_filename,
        )
        full_transcript = await exectServ.transcribe_chunks(
            chunk_paths, engine, diarize_flag, speakers, predicted_lang
        )

        end_time = datetime.datetime.now()
        duration = end_time - start_time

        if os.path.isdir(diarized_folder):
            shutil.rmtree(diarized_folder)
        if os.path.isdir(output_folder):
            shutil.rmtree(output_folder)
        if os.path.isdir(enhanced_folder):
            shutil.rmtree(enhanced_folder)

        return {
            "processed-time": duration,
            "processed-file": processed_filename,
            "transcription": full_transcript,
            "predicted-lang": predicted_lang,
            "engine": engine,
            "vet_id": vet_id,
            "diarization": diarize_flag,
            "noise-reduction": noise_remover,
            "speech-enhancement": speech_enhancement,
        }

    # refers to ("/upload_voice")
    async def voice_embeddings(self, doctor_speech_file: UploadFile) -> dict:

        original_filename = doctor_speech_file.filename
        config_name = await diarization_instance.create_embeddings(
            exectServ.output_path, doctor_speech_file, original_filename
        )

        return {
            "message": "Successfully created and saved vet voice embeddings config file!",
            "processed-audio": original_filename,
            "config-file": config_name,
        }

    
executeFunc = SpeechExecutedFunctions()
exectServ = SpeechRecognitionService()

improve and fix bugs