from msvcrt import getch
from os import listdir, makedirs, path
from shutil import rmtree
from typing import Dict, List

from natsort import natsorted

from constants import (WORKING_SPACE,
                       WORKING_SPACE_OUTPUT,
                       WORKING_SPACE_TEMP,
                       WORKING_SPACE_TEMP_MAIN_SUBS,
                       WORKING_SPACE_TEMP_ALT_SUBS,
                       console)

from data.settings import Settings

from modules.mkvtoolnix import MkvToolNix
from modules.subtitle import SubtitleRefactor
from modules.subtitle_to_speech import SubtitleToSpeech
from modules.translator import SubtitleTranslator
from modules.mkv_processing import MKVProcessing

from utils.cool_animation import CoolAnimation
from utils.execution_timer import execution_timer


def check_and_create_directories(directories: List[str]):  # ✅
    """
    Checks if the given directories exist, and if not, creates them.

    Args:
        directories (List[str]): A list of directory paths to check and create.
    """
    for directory in directories:
        if not path.exists(directory):
            makedirs(directory)


def display_logo():  # ✅
    """
        Displays the logo of the application using the CoolAnimation class.
    """
    mm_avh_logo: CoolAnimation = CoolAnimation()
    mm_avh_logo.display()
    console.print(
        '╚═══ Multimedia Magic – Audio Visual Heaven ═══╝\n', style='white_bold')


def ask_user(question: str) -> bool:
    """
        Asks the user a yes/no question and returns the response.

        Args:
            question (str): The question to ask the user.

        Returns:
            bool: True if the user answers yes, False otherwise.
    """
    console.print(question, style='bold green', end=' ')
    return input().lower() in ('t', 'y')


def update_settings() -> Settings:  # ✅
    """
        Asks the user if they want to update the settings. If yes, updates the settings and saves them to a file.

        Returns:
            Settings: The updated settings.
    """
    if ask_user('💾 Czy chcesz zmienić ustawienia? (T lub Y - tak):'):
        Settings.change_settings_save_to_file()
        console.print('Zapisano ustawienia.\n', style='green_bold')
    else:
        console.print('Pomijam tę opcję.\n', style='red_bold')
    return Settings.load_from_file()


def extract_tracks_from_mkv():  # ✅
    """
        Asks the user if they want to extract tracks from MKV files. If yes, extracts the tracks.
    """
    if ask_user('🧲 Czy chcesz wyciągnąć ścieżki z plików mkv? (T lub Y - tak):'):
        files: List[str] = get_mkv_files(WORKING_SPACE)
        sorted_files: List[str] = natsorted(files)
        for filename in sorted_files:
            mkv: MkvToolNix = MkvToolNix(filename)
            mkv.mkv_extract_track(mkv.get_mkv_info())
    else:
        console.print('Pomijam tę opcję.\n', style='red_bold')


def get_mkv_files(directory: str) -> List[str]:
    """
        Gets all MKV files in a directory.

        Args:
            directory (str): The directory to search for MKV files.

        Returns:
            List[str]: A list of MKV files in the directory.
    """
    return [file for file in listdir(directory)
            if path.isfile(path.join(directory, file)) and file.endswith('.mkv')]


def refactor_subtitles():  # ✅
    """
        Refactors subtitles in various formats to a standard format.
    """
    subtitle_extensions: List[str] = [
        '.sup', '.txt', '.ogg',
        '.ssa', '.ass', '.srt',
        '.sub', '.usf', '.vtt',
    ]

    files: List[str] = get_files_with_extensions(
        WORKING_SPACE_TEMP, subtitle_extensions)
    sorted_files = natsorted(files)
    for filename in sorted_files:
        refactor_subtitle_file(filename)


def get_files_with_extensions(directory: str, extensions: List[str]) -> List[str]:
    """
        Gets all files in a directory with certain extensions.

        Args:
            directory (str): The directory to search for files.
            extensions (List[str]): The extensions to look for.

        Returns:
            List[str]: A list of files in the directory with the specified extensions.
    """
    return [
        file for file in listdir(directory)
        if (
            path.isfile(path.join(directory, file)) and
            any(file.endswith(ext) for ext in extensions)
        )
    ]


def refactor_subtitle_file(filename: str):
    """
        Refactors a subtitle file to a standard format.

        Args:
            filename (str): The name of the subtitle file to refactor.
    """
    subtitle: SubtitleRefactor = SubtitleRefactor(filename)
    if filename.endswith('.ass') or filename.endswith('.ssa'):
        subtitle.split_ass()
        subtitle.ass_to_srt()
    if filename.endswith('.srt'):
        subtitle.move_srt()
    if filename.endswith('.txt'):
        subtitle.txt_to_srt(10)


def translate_subtitles(settings: Settings):  # ✅
    """
        Asks the user if they want to translate subtitle files. If yes, translates the files.

        Args:
        settings (Settings): The settings to use for translation.
    """
    if not ask_user('💭 Czy chcesz tłumaczyć pliki napisów? (T lub Y - tak):'):
        console.print('Pomijam tę opcję.\n', style='red_bold')
        return

    main_subs_files = get_srt_files(WORKING_SPACE_TEMP_MAIN_SUBS)
    files_to_translate = ask_to_translate_files(main_subs_files)
    translate_files(files_to_translate, settings)


def get_srt_files(directory: str) -> List[str]:
    """
        Gets all SRT files in a directory.

        Args:
            directory (str): The directory to search for SRT files.

        Returns:
            List[str]: A list of SRT files in the directory.
    """
    return [
        filename for filename in listdir(directory)
        if path.isfile(path.join(directory, filename)) and filename.endswith('.srt')
    ]


def ask_to_translate_files(files: List[str]) -> dict:
    """
        Asks the user which files they want to translate.

        Args:
            files (List[str]): A list of files to ask about.

        Returns:
            dict: A dictionary mapping file names to a boolean indicating whether the user wants to translate them.
    """
    files_to_translate: dict = {}
    for filename in files:
        console.print("\nTŁUMACZENIE PLIKU:", style='yellow_bold')
        console.print(filename, style='white_bold')
        if ask_user("Czy chcesz przetłumaczyć (T lub Y - tak):"):
            files_to_translate[filename] = True
        else:
            console.print('Pomijam tę opcję.\n', style='red_bold')
            files_to_translate[filename] = False
    return files_to_translate


def translate_files(files_to_translate: dict, settings: Settings):
    """
        Translates the specified files.

        Args:
            files_to_translate (dict): A dictionary mapping file names to a boolean indicating whether to translate them.
            settings (Settings): The settings to use for translation.
    """
    translator_instance: SubtitleTranslator = SubtitleTranslator()
    for filename, should_translate in files_to_translate.items():
        if should_translate:
            translator_instance.translate_srt(filename,
                                              WORKING_SPACE_TEMP_MAIN_SUBS,
                                              settings)
            if path.exists(path.join(WORKING_SPACE_TEMP_ALT_SUBS, filename)):
                translator_instance.translate_srt(filename,
                                                  WORKING_SPACE_TEMP_ALT_SUBS,
                                                  settings)


def convert_numbers_to_words():  # ✅
    """
        Asks the user if they want to convert numbers to words in the text. If yes, performs the conversion.
    """
    if not ask_user('🔢 Czy chcesz przekonwertować liczby na słowa w tekście? (T lub Y - tak):'):
        console.print('Pomijam tę opcję.\n', style='red_bold')
        return

    srt_files = get_srt_files(WORKING_SPACE_TEMP_MAIN_SUBS)
    convert_numbers_in_files(srt_files)


def get_srt_files(directory: str) -> List[str]:
    """
        Gets all SRT files in a directory.

        Args:
            directory (str): The directory to search for SRT files.

        Returns:
            List[str]: A list of SRT files in the directory.
    """
    return [
        file for file in listdir(directory)
        if path.isfile(path.join(directory, file)) and file.endswith('.srt')
    ]


def convert_numbers_in_files(files: List[str]):
    """
        Converts numbers to words in the specified files.

        Args:
            files (List[str]): A list of files to convert numbers in.
    """
    for filename in files:
        console.print(
            "\nKONWERSJA LICZB (BEZ POPRAWNOŚCI GRAMATYCZNEJ) W PLIKU:", style='yellow_bold')
        console.print(filename, style='white_bold')
        if ask_user("Czy chcesz przekonwertować liczby na słowa w tym pliku? (T lub Y - tak):"):
            subtitle: SubtitleRefactor = SubtitleRefactor(filename)
            subtitle.convert_numbers_in_srt()
        else:
            console.print(f'Pomijam plik {filename}.\n', style='red_bold')


def generate_audio_for_subtitles(settings: Settings) -> None:  # ✅
    """
        Asks the user if they want to generate audio for subtitles. If yes, generates the audio.

        Args:
            settings (Settings): The settings to use for audio generation.
    """
    if not ask_user('🎤 Czy chcesz generować audio dla napisów? (T lub Y - tak):'):
        console.print('Pomijam tę opcję.\n', style='red_bold')
        return

    main_subs_files: List[str] = get_srt_files(WORKING_SPACE_TEMP_MAIN_SUBS)
    files_to_generate_audio: Dict[str, bool] = ask_to_generate_audio_files(
        main_subs_files)
    generate_audio_files(files_to_generate_audio, settings)


def ask_to_generate_audio_files(files: List[str]) -> Dict[str, bool]:
    """
        Asks the user which files they want to generate audio for.

        Args:
            files (List[str]): A list of files to ask about.

        Returns:
            dict: A dictionary mapping file names to a boolean indicating whether the user wants to generate audio for them.
    """
    files_to_generate_audio: Dict[str, bool] = {}
    for filename in files:
        console.print("\nGENEROWANIE AUDIO DLA PLIKU:", style='yellow_bold')
        console.print(filename, style='white_bold')
        if ask_user("Czy chcesz wygenerować audio dla tego pliku? (T lub Y - tak):"):
            files_to_generate_audio[filename] = True
        else:
            console.print('Pomijam tę opcję.', style='red_bold')
            files_to_generate_audio[filename] = False
    return files_to_generate_audio


def generate_audio_files(files_to_generate_audio: Dict[str, bool], settings: Settings) -> None:
    """
        Generates audio for the specified files.

        Args:
            files_to_generate_audio (Dict[str, bool]): A dictionary mapping file names to a boolean indicating whether the user wants to generate audio for them.
            settings (Settings): The settings to use for audio generation.
    """
    audio_generator: SubtitleToSpeech
    if 'TTS - *Głos* - ElevenLans' in settings.tts:
        audio_generator = SubtitleToSpeech('')
        audio_generator.srt_to_eac3_elevenlabs()
    else:
        for filename, should_generate_audio in files_to_generate_audio.items():
            if should_generate_audio:
                audio_generator = SubtitleToSpeech(filename)
                audio_generator.generate_audio(settings)


def refactor_alt_subtitles():  # ✅
    """
        Refactors alternative subtitles to a standard format.
    """
    files: List[str] = get_srt_files(WORKING_SPACE_TEMP_ALT_SUBS)
    sorted_files = natsorted(files)
    for filename in sorted_files:
        subtitle: SubtitleRefactor = SubtitleRefactor(filename)
        subtitle.srt_to_ass()


def process_output_files(settings: Settings):
    """
        Processes output files based on user settings.

        Args:
            settings (Settings): The settings to use for processing.
    """
    files = listdir(WORKING_SPACE_OUTPUT)
    files_dict = {path.splitext(file)[0]: [] for file in files}
    for file in files:
        if not file.endswith(('.mkv', '.mp4')):
            files_dict[path.splitext(file)[0]].append(file)

    for base_name, files in files_dict.items():
        if len(files) > 0:
            # https://trac.ffmpeg.org/wiki/Encode/H.264
            # crf_value => 0 ... 18 ... 23 ... 51 ... :(
            # preset_value => 'ultrafast', 'superfast', 'veryfast', 'faster', 'fast', 'medium', 'slow', 'slower', 'veryslow', 'placebo'
            subtitle_processor = MKVProcessing(filename=base_name,
                                               crf_value='18',
                                               preset_value='medium')
            subtitle_processor.process_mkv(settings)


def clear_temp_folders():
    """
        Clears temporary folders used during processing.
    """
    folders = [WORKING_SPACE_TEMP, WORKING_SPACE_TEMP_MAIN_SUBS,
               WORKING_SPACE_TEMP_ALT_SUBS]
    for folder in folders:
        rmtree(folder, ignore_errors=True)
        makedirs(folder, exist_ok=True)


@execution_timer  # ✅
def main():
    """
        Main function that runs the entire process.
    """
    display_logo()
    settings: Settings = update_settings()
    extract_tracks_from_mkv()
    refactor_subtitles()
    translate_subtitles(settings)
    convert_numbers_to_words()
    generate_audio_for_subtitles(settings)
    refactor_alt_subtitles()
    process_output_files(settings)
    clear_temp_folders()


if __name__ == '__main__':
    """
        Ensures the main function is only run if the script is executed directly (not imported as a module).
    """
    directories: List[str] = [WORKING_SPACE, WORKING_SPACE_OUTPUT,
                              WORKING_SPACE_TEMP, WORKING_SPACE_TEMP_MAIN_SUBS, WORKING_SPACE_TEMP_ALT_SUBS]
    check_and_create_directories(directories)
    main()
    console.print(
        '\n[green_italic]Naciśnij dowolny klawisz, aby zakończyć działanie programu...', end='')
    getch()