import yt_dlp
import os
import sys
from typing import Dict, Any, List, Optional


def get_video_info(url: str) -> Optional[Dict[str, Any]]:
    """Получает информацию о видео и доступных форматах"""
    ydl_options = {
        'quiet': True,
        'no_warnings': True,
    }

    try:
        with yt_dlp.YoutubeDL(ydl_options) as ydl:
            info = ydl.extract_info(url, download=False)
            return info
    except Exception as error_info:
        print(f"❌ Ошибка получения информации: {error_info}")
        return None


def display_audio_formats(info: Dict[str, Any]) -> List[Dict[str, Any]]:
    """Отображает все доступные аудио форматы"""
    if not info:
        return []

    print(f"\n📺 Видео: {info.get('title', 'Без названия')}")
    print(f"👤 Автор: {info.get('uploader', 'Неизвестно')}")
    print(f"⏱️ Длительность: {info.get('duration', 0)} сек")

    view_count = info.get('view_count', 0)
    if isinstance(view_count, int):
        print(f"👁️ Просмотры: {view_count:,}")
    else:
        print(f"👁️ Просмотры: {view_count}")

    print("\n🔊 Доступные аудио форматы:")
    print("=" * 80)
    print(
        f"{'№':3} | {'ID':6} | {'Качество':8} | {'Кодек':10} | {'Формат':8} | {'Размер':10} | {'Частота':10}")
    print("-" * 80)

    audio_formats = []
    format_index = 0

    # Проходим по всем форматам
    for fmt in info.get('formats', []):
        audio_codec_value = fmt.get('acodec', 'none')
        video_codec_value = fmt.get('vcodec', 'none')

        if audio_codec_value != 'none' and audio_codec_value is not None and video_codec_value == 'none':
            format_index += 1
            format_id = fmt.get('format_id', 'N/A')
            abr = fmt.get('abr', 0)

            # Извлекаем кодек без дополнительных параметров
            if isinstance(audio_codec_value, str):
                audio_codec_clean = audio_codec_value.split('.')[
                    0] if '.' in audio_codec_value else audio_codec_value
            else:
                audio_codec_clean = str(audio_codec_value)

            ext = fmt.get('ext', 'N/A')

            # Размер файла
            filesize = fmt.get('filesize', fmt.get('filesize_approx', 0))
            if filesize:
                if filesize > 1024 * 1024:
                    size_str = f"{filesize / (1024 * 1024):.1f} MB"
                else:
                    size_str = f"{filesize / 1024:.1f} KB"
            else:
                size_str = "N/A"

            # Частота дискретизации
            asr_value = fmt.get('asr', 0)
            if asr_value:
                asr_str = f"{asr_value / 1000:.1f} kHz"
            else:
                asr_str = "N/A"

            # Добавляем информацию о формате
            audio_format_info = {
                'index': format_index,
                'id': format_id,
                'abr': abr,
                'audio_codec': audio_codec_clean,
                'ext': ext,
                'filesize': filesize,
                'asr': asr_value,
                'format_note': fmt.get('format_note', '')
            }
            audio_formats.append(audio_format_info)

            # Выводим информацию о формате
            print(
                f"{format_index:3} | {format_id:6} | {abr:>6} kbps | {audio_codec_clean:>10} | "
                f"{ext:>8} | {size_str:>10} | {asr_str:>10}")

            # Добавляем описание формата
            format_note = fmt.get('format_note', '')
            if format_note:
                print(f"     ↳ Примечание: {format_note}")

    print("=" * 80)
    return audio_formats


def get_format_choice(audio_formats: List[Dict[str, Any]]) -> Optional[str]:
    """Получает выбор формата от пользователя"""
    if not audio_formats:
        return None

    while True:
        try:
            choice = input(
                f"\n🎵 Выберите формат для скачивания [1-{len(audio_formats)}] (Enter - лучший): ").strip()

            if not choice:
                best_format = max(audio_formats, key=lambda x: x.get('abr', 0))
                print(
                    f"✅ Выбран лучший формат: {best_format.get('id')} ({best_format.get('abr')} kbps)")
                return best_format.get('id')

            choice_int = int(choice)
            if 1 <= choice_int <= len(audio_formats):
                selected = audio_formats[choice_int - 1]
                print(
                    f"✅ Выбран формат: {selected.get('id')} ({selected.get('abr')} kbps, {selected.get('audio_codec')})")
                return selected.get('id')
            else:
                print(
                    f"❌ Пожалуйста, введите число от 1 до {len(audio_formats)}")

        except ValueError:
            print("❌ Пожалуйста, введите корректный номер")


def get_output_settings() -> Dict[str, Any]:
    """Получает настройки выходного формата от пользователя"""
    print("\n🎚️  Настройки выходного формата:")
    print("-" * 40)

    # Выбор формата
    print("Доступные форматы:")
    formats = {
        '1': {'name': 'MP3', 'ext': 'mp3', 'codec': 'mp3'},
        '2': {'name': 'M4A (AAC)', 'ext': 'm4a', 'codec': 'aac'},
        '3': {'name': 'Opus', 'ext': 'opus', 'codec': 'opus'},
        '4': {'name': 'FLAC', 'ext': 'flac', 'codec': 'flac'},
        '5': {'name': 'WAV', 'ext': 'wav', 'codec': 'wav'},
        '6': {'name': 'Оригинальный формат', 'ext': 'original',
              'codec': 'original'}
    }

    for key, fmt in formats.items():
        print(f"  {key}. {fmt.get('name')}")

    while True:
        format_choice = input(
            "Выберите выходной формат [1-6] (Enter - MP3): ").strip()
        if not format_choice:
            format_choice = '1'

        if format_choice in formats:
            selected_format = formats[format_choice]
            break
        else:
            print("❌ Пожалуйста, выберите корректный вариант")

    # Настройка качества
    quality_value = '320'

    if selected_format.get('codec') == 'mp3':
        print("\n🎚️  Качество MP3:")
        print("  1. 320 kbps (отличное, максимальное)")
        print("  2. 256 kbps (очень хорошее)")
        print("  3. 192 kbps (хорошее, стандартное)")
        print("  4. 128 kbps (приемлемое)")

        while True:
            quality_choice = input(
                "Выберите качество [1-4] (Enter - 320): ").strip()
            if not quality_choice:
                quality_choice = '1'

            quality_map = {'1': '320', '2': '256', '3': '192', '4': '128'}
            if quality_choice in quality_map:
                quality_value = quality_map[quality_choice]
                break
            else:
                print("❌ Пожалуйста, выберите корректный вариант")

    elif selected_format.get('codec') == 'aac':
        print("\n🎚️  Качество AAC:")
        print("  1. 256 kbps (отличное)")
        print("  2. 192 kbps (хорошее)")
        print("  3. 128 kbps (стандартное YouTube)")
        print("  4. 96 kbps (экономное)")

        while True:
            quality_choice = input(
                "Выберите качество [1-4] (Enter - 256): ").strip()
            if not quality_choice:
                quality_choice = '1'

            quality_map = {'1': '256', '2': '192', '3': '128', '4': '96'}
            if quality_choice in quality_map:
                quality_value = quality_map[quality_choice]
                break
            else:
                print("❌ Пожалуйста, выберите корректный вариант")

    elif selected_format.get('codec') == 'opus':
        print("\n🎚️  Качество Opus (0-10, где 10 - лучшее):")
        print("  1. 10 (отличное, ~160 kbps)")
        print("  2. 8 (очень хорошее, ~128 kbps)")
        print("  3. 6 (хорошее, ~96 kbps)")
        print("  4. 4 (экономное, ~64 kbps)")

        while True:
            quality_choice = input(
                "Выберите качество [1-4] (Enter - 10): ").strip()
            if not quality_choice:
                quality_choice = '1'

            quality_map = {'1': '10', '2': '8', '3': '6', '4': '4'}
            if quality_choice in quality_map:
                quality_value = quality_map[quality_choice]
                break
            else:
                print("❌ Пожалуйста, выберите корректный вариант")

    elif selected_format.get('codec') in ['flac', 'wav']:
        quality_value = '0'

    elif selected_format.get('codec') == 'original':
        quality_value = '0'

    # Настройка метаданных
    print("\n📝 Настройки метаданных:")
    embed_thumbnail = input(
        "Встроить обложку в аудио файл? [y/N]: ").strip().lower() == 'y'
    add_metadata_input = input(
        "Добавить метаданные (исполнитель, название и т.д.)? [Y/n]: ").strip().lower()
    add_metadata = add_metadata_input != 'n'

    # Папка для сохранения
    print("\n📁 Настройки сохранения:")
    default_output = os.path.join(os.path.expanduser("~"), "Downloads",
                                  "YouTube Audio")
    output_folder = input(
        f"Папка для сохранения (Enter - '{default_output}'): ").strip()
    if not output_folder:
        output_folder = default_output

    # Создаем папку, если не существует
    os.makedirs(output_folder, exist_ok=True)

    # Сохранять оригинальный файл
    keep_original_input = input(
        "Сохранять оригинальный файл после конвертации? [y/N]: ").strip().lower()
    keep_original = keep_original_input == 'y'

    return {
        'format': selected_format,
        'quality': quality_value,
        'embed_thumbnail': embed_thumbnail,
        'add_metadata': add_metadata,
        'output_folder': output_folder,
        'keep_original': keep_original
    }


def progress_hook(data: Dict[str, Any]) -> None:
    """Отображает прогресс скачивания"""
    if data.get('status') == 'downloading':
        percent = data.get('_percent_str', '0%').strip()
        speed = data.get('_speed_str', 'N/A')
        eta = data.get('_eta_str', 'N/A')
        print(f"\r📥 Скачиваю: {percent} | Скорость: {speed} | Осталось: {eta}",
              end='', flush=True)
    elif data.get('status') == 'finished':
        print(f"\r✅ Скачивание завершено! Конвертирую...")


def download_audio(url: str, format_id: str,
                   output_settings: Dict[str, Any]) -> bool:
    """Скачивает аудио с заданными настройками"""

    # Создаем опции для yt-dlp с правильными типами
    ydl_options = {
        'format': format_id,
        'outtmpl': os.path.join(output_settings.get('output_folder', './'),
                                '%(title)s.%(ext)s'),
        'quiet': False,
        'no_warnings': False,
        'keepvideo': output_settings.get('keep_original', False),
        'progress_hooks': [progress_hook],
    }

    # Инициализируем список постпроцессоров
    postprocessors_list = []

    # Если выбран не оригинальный формат, добавляем конвертацию
    if output_settings.get('format', {}).get('codec') != 'original':
        # Основной постпроцессор для конвертации
        postprocessors_list.append({
            'key': 'FFmpegExtractAudio',
            'preferredcodec': output_settings.get('format', {}).get('codec',
                                                                    'mp3'),
            'preferredquality': output_settings.get('quality', '320'),
        })

        # Метаданные
        if output_settings.get('add_metadata', False):
            ydl_options['writethumbnail'] = output_settings.get(
                'embed_thumbnail', False)
            postprocessors_list.append({
                'key': 'FFmpegMetadata',
                'add_metadata': True,
            })

        # Обложка
        if output_settings.get('embed_thumbnail', False):
            ydl_options['writethumbnail'] = True
            postprocessors_list.append({
                'key': 'EmbedThumbnail',
                'already_have_thumbnail': False,
            })

    # Добавляем постпроцессоры, если они есть
    if postprocessors_list:
        ydl_options['postprocessors'] = postprocessors_list

    print(f"\n🚀 Начинаю скачивание...")
    print(f"📁 Сохраняю в: {output_settings.get('output_folder', './')}")
    print(
        f"🎵 Выходной формат: {output_settings.get('format', {}).get('name', 'MP3')}")

    if output_settings.get('format', {}).get('codec') != 'original':
        print(f"⚡ Качество: {output_settings.get('quality', '320')}")

    try:
        with yt_dlp.YoutubeDL(ydl_options) as ydl:
            info = ydl.extract_info(url, download=True)

            # Получаем имя файла
            if output_settings.get('format', {}).get('codec') == 'original':
                original_ext = 'webm'
                if isinstance(info, dict):
                    for fmt in info.get('requested_formats', []):
                        if fmt.get('format_id') == format_id:
                            original_ext = fmt.get('ext', 'webm')
                            break
                ext = original_ext
            else:
                ext = output_settings.get('format', {}).get('ext', 'mp3')

            video_title = info.get('title', 'Без названия') if isinstance(info,
                                                                          dict) else 'Без названия'
            filename = os.path.join(output_settings.get('output_folder', './'),
                                    f"{video_title}.{ext}")
            print(f"\n✅ Аудио успешно скачано!")
            print(f"📄 Файл: {filename}")

            # Показываем информацию о файле
            if os.path.exists(filename):
                size = os.path.getsize(filename)
                size_mb = size / (1024 * 1024)
                print(f"📦 Размер: {size_mb:.2f} MB")

            return True

    except Exception as download_error:
        print(f"\n❌ Ошибка при скачивании: {download_error}")
        return False


def main() -> None:
    """Основная функция программы"""

    print("=" * 60)
    print("🎵 YouTube Audio Downloader Pro")
    print("=" * 60)

    # Получаем URL
    url = input("\n🔗 Введите ссылку на YouTube видео: ").strip()
    if not url:
        print("❌ Не введена ссылка!")
        return

    # Получаем информацию о видео
    print("\n🔍 Получаю информацию о видео...")
    info = get_video_info(url)
    if not info:
        print("❌ Не удалось получить информацию о видео")
        return

    # Показываем доступные форматы
    audio_formats = display_audio_formats(info)
    if not audio_formats:
        print("❌ Не найдено доступных аудио форматов")
        return

    # Выбираем формат для скачивания
    format_id = get_format_choice(audio_formats)
    if not format_id:
        print("❌ Не выбран формат для скачивания")
        return

    # Получаем настройки вывода
    output_settings = get_output_settings()

    # Подтверждение
    print("\n" + "=" * 60)
    print("📋 Подтвердите настройки:")
    print(f"📺 Видео: {info.get('title', 'Без названия')}")
    print(f"🔧 Исходный формат: {format_id}")
    print(
        f"🎵 Выходной формат: {output_settings.get('format', {}).get('name', 'MP3')}")

    if output_settings.get('format', {}).get('codec') != 'original':
        print(f"⚡ Качество: {output_settings.get('quality', '320')}")

    print(f"📁 Папка: {output_settings.get('output_folder', './')}")
    print(
        f"🖼️  Обложка: {'Да' if output_settings.get('embed_thumbnail', False) else 'Нет'}")
    print(
        f"🏷️  Метаданные: {'Да' if output_settings.get('add_metadata', False) else 'Нет'}")
    print(
        f"💾 Оригинал: {'Сохранить' if output_settings.get('keep_original', False) else 'Удалить'}")

    confirm = input("\nНачать скачивание? [Y/n]: ").strip().lower()
    if confirm not in ['', 'y', 'yes', 'да']:
        print("❌ Скачивание отменено")
        return

    # Скачиваем
    print("\n" + "=" * 60)
    success = download_audio(url, format_id, output_settings)

    if success:
        print("\n🎉 Скачивание успешно завершено!")
    else:
        print("\n😞 Произошла ошибка при скачивании")

    # Предложение скачать еще
    print("\n" + "=" * 60)
    another = input("Скачать еще одно аудио? [y/N]: ").strip().lower()
    if another in ['y', 'yes', 'да']:
        main()
    else:
        print("\n👋 До свидания!")


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\n\n👋 Программа прервана пользователем")
        sys.exit(0)
    except Exception as main_error:
        print(f"\n❌ Неожиданная ошибка: {main_error}")
        sys.exit(1)