import os
import re
import subprocess
import time


def recursive_ls_dir(directory):
    paths = [os.path.join(root, file) for root, dirs, files in os.walk(directory) for file in files]
    paths += [os.path.join(root, dir) for root, dirs, files in os.walk(directory) for dir in dirs]
    print(paths)


def download_models_to_dir(files: list[str], dir_path: str):
    """Download models to a directory using AWS CLI."""
    for f in files:
        full_path = os.path.abspath(os.path.join(dir_path, f))
        if not os.path.exists(full_path):
            parent_dir = os.path.dirname(full_path)
            os.makedirs(parent_dir, exist_ok=True)

            print("Downloading", full_path)
            cmd = [
                "aws",
                "s3",
                "cp",
                f"s3://suno-data/{f}",
                full_path,
            ]
            subprocess.run(cmd)
        else:
            print("Skipping", f)


def strip_square_brackets(text):
    return re.sub(r"\[.*?\]", "", text).strip()


def count_syllables_word(word):
    if not word:
        return 0

    word = word.lower()
    vowels = "aeiouy"
    count = 0

    if word[0] in vowels:
        count += 1

    for i in range(1, len(word)):
        if word[i] in vowels and word[i - 1] not in vowels:
            count += 1

    if word.endswith("e"):
        count -= 1

    if word.endswith("le"):
        count += 1

    if count == 0:
        count += 1

    return count


def count_syllables(text):
    word_arr = text.split()
    count = 0
    for word in word_arr:
        count += count_syllables_word(word)
    return count


def wrap_text(text, line_length=25):
    words = text.split()
    lines = []
    line = ""

    for word in words:
        if len(line) + len(word) + 1 <= line_length:
            if line:
                line += " " + word
            else:
                line += word
        else:
            lines.append(line)
            line = word

    lines.append(line)
    return "\n".join(lines)


def make_transcript_timestamps(audio, text):
    from suno_utils.tasks.asr.whisper import transcribe

    start_time = time.time()

    # Get whisper transcribe (retry it if it fails)
    history_text = transcribe(audio)
    print("whisper response ", str(history_text))
    if not history_text.get("text"):
        print("try whisper again")
        history_text = transcribe(audio)
        print("whisper response ", str(history_text))
    print(f"Made whisper transcription in {time.time() - start_time:.2f} seconds.")
    # Format whisper output
    segments = history_text["segments"]
    transcription = []
    for s in segments:
        for w in s["words"]:
            transcription.append(w)

    print(transcription)

    text = strip_square_brackets(text)
    text = text.split("\n")

    # 1. Store syllables up to each word in the original and transcribed text
    real_text = []
    for i in range(len(text)):
        word = text[i]
        val = {}
        val["word"] = word
        val["syllables"] = count_syllables(word) + (real_text[i - 1]["syllables"] if i > 0 else 0)
        real_text.append(val)

    transcribed_text = []
    for i in range(len(transcription)):
        word = transcription[i]["word"]
        val = {}
        val["word"] = word
        val["syllables"] = count_syllables(word) + (transcribed_text[i - 1]["syllables"] if i > 0 else 0)
        val["start"] = transcription[i]["start"]
        val["end"] = transcription[i]["end"]
        transcribed_text.append(val)

    # 2. Iterate through /n in original text.
    # Find first word in the transcribed text that has >= syllables and use that as start time for new line
    transcribe_word_count = 0
    for t in real_text:
        print(t)
        last_transcribed_syllable = transcribed_text[-1].get("syllables") if transcribed_text else 0
        print("last_transcribed_syllable is", last_transcribed_syllable)
        if t["syllables"] > last_transcribed_syllable:
            t["did_not_reach"] = True
            print("  did not reach")
        while transcribe_word_count < len(transcribed_text):
            transcribed_word = transcribed_text[transcribe_word_count]
            if transcribed_word["syllables"] >= t["syllables"]:
                t["end_time"] = transcribed_word["end"]
                next_transcribed_word = (
                    transcribed_text[transcribe_word_count + 1]
                    if transcribe_word_count + 1 < len(transcribed_text)
                    else transcribed_word
                )
                t["start_time"] = (
                    next_transcribed_word["start"]
                    if transcribe_word_count + 1 < len(transcribed_text)
                    else transcribed_word["end"]
                )
                print(
                    "start of next word",
                    next_transcribed_word["word"],
                    next_transcribed_word["start"],
                    next_transcribed_word["end"],
                    str(transcribe_word_count + 1 < len(transcribed_text)),
                )
                print(
                    "MATCH: ",
                    transcribe_word_count,
                    len(transcribed_text),
                    transcribed_word,
                )
                break
            transcribe_word_count += 1

    # Generate timestamps array (which is used by video). 't' stores timestamp, 'l' stores lyrics
    timestamps = []
    for i in range(len(real_text)):
        if real_text[i] and real_text[i].get("did_not_reach") is None or False:
            val = {}
            val["l"] = wrap_text(real_text[i]["word"])
            end_time_str = real_text[i - 1].get("end_time") if i > 0 else 0
            start_time_str = real_text[i - 1].get("start_time") if i > 0 else 0
            val["t"] = (
                round((float(end_time_str) + float(start_time_str)) / 2, 2)
                if end_time_str is not None and start_time_str is not None
                else timestamps[-1]["t"]
            )
            if i > 0 and val["t"] == timestamps[-1]["t"]:
                timestamps[-1]["l"] += "\n" + val["l"]
                timestamps[-1]["l"] = wrap_text(timestamps[-1]["l"])
            else:
                val["l"] = wrap_text(val["l"])
                timestamps.append(val)
    print(timestamps)
    return timestamps


def retry_decorator(max_retries=3, wait_seconds=1, silent_return: bool = False):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_retries + 1):
                try:
                    result = func(*args, **kwargs)
                    return result  # If successful, return the result
                except Exception as e:
                    print(
                        f"Attempt {attempt} failed: {str(e)}. \
                        Input args are {[str(x) for x in args]}. \
                        Input kwargs are {[str(k) + ':' + str(v) for k, v in kwargs.items()]}"
                    )
                    if attempt < max_retries:
                        print(f"Retrying in {wait_seconds} seconds...")
                        time.sleep(wait_seconds)
                    else:
                        print(f"All {max_retries} attempts failed. Giving up.")
                        if silent_return:
                            return None
                        else:
                            raise FileNotFoundError(f"Timed out retry because of {str(e)}")

        return wrapper

    return decorator


def ffmpeg_stream_encode(gain_adjust: float = 0):
    return subprocess.Popen(
        [
            "ffmpeg",
            "-f",
            "f32le",
            "-acodec",
            "pcm_f32le",
            "-ar",
            "48000",
            "-ac",
            "2",
            "-i",
            "pipe:",
            "-af",
            f"volume={str(float(gain_adjust))}dB,alimiter=limit=0.95:attack=5:release=50:level=0",
            "-aq",
            "2",
            "-flush_packets",
            "1",
            "-f",
            "mp3",
            "pipe:",
        ],
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.DEVNULL,
        bufsize=0,
    )


def ffmpeg_stream_encode_opus_webm(gain_adjust: float = 0):
    return subprocess.Popen(
        [
            "ffmpeg",
            "-f",
            "f32le",
            "-acodec",
            "pcm_f32le",
            "-ar",
            "48000",
            "-ac",
            "2",
            "-i",
            "pipe:",
            "-af",
            f"volume={str(float(gain_adjust))}dB,alimiter=limit=0.95:attack=5:release=50:level=0",
            "-c:a",
            "libopus",
            "-b:a",
            "128k",
            "-vbr",
            "1",
            "-flush_packets",
            "1",
            "-f",
            "webm",
            "pipe:",
        ],
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.DEVNULL,
        bufsize=0,
    )


def print_gpu_memory_usage(class_name: str = None):
    import torch

    t = torch.cuda.get_device_properties(0).total_memory
    r = torch.cuda.memory_reserved(0)
    a = torch.cuda.memory_allocated(0)
    print(
        f"{class_name} GPU memory usage: allocated {a / 1e9:.2f} GB, reserved {r / 1e9:.2f} GB, total {t / 1e9:.2f} GB"
    )
