#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
YouTube MP3 Downloader v2.0
Изисква: yt-dlp, ffmpeg
Инсталация: pip install yt-dlp
За ffmpeg: https://ffmpeg.org/download.html
"""

import os
import sys
import json
import subprocess
import platform
from pathlib import Path
from datetime import datetime
from typing import Optional, Dict, Any

# Проверка за Python версия
if sys.version_info < (3, 7):
    print("❌ Нужна е Python 3.7 или по-нова!")
    sys.exit(1)

# Опит за импорт на yt-dlp
try:
    import yt_dlp
except ImportError:
    print("❌ yt-dlp не е инсталиран!")
    print("📦 Изпълни: pip install yt-dlp")
    sys.exit(1)

# ========== КЛАСОВЕ И КОНФИГУРАЦИЯ ==========

class Colors:
    """Цветове за терминала"""
    HEADER = '\033[95m'
    BLUE = '\033[94m'
    CYAN = '\033[96m'
    GREEN = '\033[92m'
    YELLOW = '\033[93m'
    RED = '\033[91m'
    RESET = '\033[0m'
    BOLD = '\033[1m'
    DIM = '\033[2m'

class Config:
    """Управление на конфигурацията"""
    def __init__(self):
        self.config_file = Path.home() / ".ytmp3downloader.json"
        self.default_config = {
            "quality": "320",
            "output_dir": str(Path.home() / "Desktop" / "YouTube MP3"),
            "embed_thumbnail": True,
            "add_metadata": True,
            "extract_audio": True,
            "last_updated": datetime.now().isoformat()
        }
        self.load()
    
    def load(self):
        """Зареждане на конфигурацията"""
        if self.config_file.exists():
            try:
                with open(self.config_file, 'r', encoding='utf-8') as f:
                    saved = json.load(f)
                    self.config = {**self.default_config, **saved}
            except:
                self.config = self.default_config.copy()
        else:
            self.config = self.default_config.copy()
    
    def save(self):
        """Запазване на конфигурацията"""
        self.config['last_updated'] = datetime.now().isoformat()
        with open(self.config_file, 'w', encoding='utf-8') as f:
            json.dump(self.config, f, indent=2, ensure_ascii=False)
    
    def get(self, key: str, default=None):
        return self.config.get(key, default)
    
    def set(self, key: str, value):
        self.config[key] = value
        self.save()

class YouTubeDownloader:
    """Основен клас за сваляне"""
    
    def __init__(self):
        self.config = Config()
        self.check_dependencies()
    
    @staticmethod
    def clear_screen():
        """Изчистване на екрана"""
        os.system('cls' if platform.system() == 'Windows' else 'clear')
    
    @staticmethod
    def print_header():
        """Показване на хедъра"""
        YouTubeDownloader.clear_screen()
        print(f"\n{Colors.RED}  ╔══════════════════════════════════════════╗{Colors.RESET}")
        print(f"{Colors.RED}  ║       YouTube  →  MP3  Downloader v2.0   ║{Colors.RESET}")
        print(f"{Colors.DIM}  ║           powered by yt-dlp              ║{Colors.RESET}")
        print(f"{Colors.RED}  ╚══════════════════════════════════════════╝{Colors.RESET}")
        print("")
    
    def print_ok(self, msg: str):
        print(f"  {Colors.GREEN}✓{Colors.RESET} {msg}")
    
    def print_error(self, msg: str):
        print(f"  {Colors.RED}✗{Colors.RESET} {msg}")
    
    def print_info(self, msg: str):
        print(f"  {Colors.CYAN}ℹ{Colors.RESET} {msg}")
    
    def print_warning(self, msg: str):
        print(f"  {Colors.YELLOW}⚠{Colors.RESET} {msg}")
    
    def print_step(self, msg: str):
        print(f"  {Colors.BLUE}→{Colors.RESET} {msg}")
    
    def check_ffmpeg(self) -> bool:
        """Проверка за ffmpeg"""
        try:
            result = subprocess.run(
                ['ffmpeg', '-version'],
                capture_output=True,
                text=True,
                creationflags=subprocess.CREATE_NO_WINDOW if platform.system() == 'Windows' else 0
            )
            if result.returncode == 0:
                version = result.stdout.split('\n')[0].replace('ffmpeg version', '').strip()
                self.print_ok(f"ffmpeg {version}")
                return True
        except:
            pass
        
        self.print_error("ffmpeg не е намерен!")
        self.print_warning("Изтегли от: https://ffmpeg.org/download.html")
        return False
    
    def check_dependencies(self):
        """Проверка на всички зависимости"""
        self.print_info("Проверка на зависимости...")
        
        # Проверка за yt-dlp (вече е импортнат)
        try:
            import yt_dlp
            self.print_ok(f"yt-dlp {yt_dlp.version.__version__}")
        except:
            self.print_error("yt-dlp не е намерен!")
            self.print_warning("Изпълни: pip install yt-dlp")
            sys.exit(1)
        
        # Проверка за ffmpeg
        if not self.check_ffmpeg():
            self.print_warning("Ще продължа, но някои функции няма да работят!")
        
        print("")
    
    def get_video_info(self, url: str) -> Optional[Dict[str, Any]]:
        """Взема информация за видеото"""
        try:
            ydl_opts = {
                'quiet': True,
                'no_warnings': True,
                'extract_flat': False,
            }
            
            with yt_dlp.YoutubeDL(ydl_opts) as ydl:
                info = ydl.extract_info(url, download=False)
                return info
        except Exception as e:
            self.print_error(f"Грешка при получаване на информация: {str(e)}")
            return None
    
    def select_quality(self) -> str:
        """Избор на качество"""
        print(f"  {Colors.BOLD}Качество на MP3:{Colors.RESET}")
        print(f"    1) 320 kbps  {Colors.DIM}(най-добро){Colors.RESET}")
        print(f"    2) 192 kbps  {Colors.DIM}(препоръчително){Colors.RESET}")
        print(f"    3) 128 kbps  {Colors.DIM}(компактно){Colors.RESET}")
        print(f"  {Colors.DIM}(Enter = {self.config.get('quality')} kbps){Colors.RESET}")
        
        choice = input("\n  Избор [1-3]: ").strip()
        
        quality_map = {
            "1": "320",
            "2": "192",
            "3": "128"
        }
        
        quality = quality_map.get(choice, self.config.get('quality'))
        self.config.set('quality', quality)
        return quality
    
    def select_output_dir(self) -> str:
        """Избор на изходна папка"""
        default = self.config.get('output_dir')
        print(f"  {Colors.BOLD}Папка за запис:{Colors.RESET}")
        print(f"  {Colors.DIM}(Enter = {default}){Colors.RESET}")
        
        user_input = input("  Папка: ").strip().strip('"').strip("'")
        
        if not user_input:
            output_dir = default
        else:
            output_dir = user_input
        
        # Създаване на папката ако не съществува
        Path(output_dir).mkdir(parents=True, exist_ok=True)
        
        self.config.set('output_dir', output_dir)
        return output_dir
    
    def download_single(self, url: str, quality: str, output_dir: str):
        """Сваляне на единично видео"""
        # Валидация на URL
        if 'youtube.com' not in url and 'youtu.be' not in url:
            self.print_error("Невалиден YouTube URL!")
            return
        
        # Показване на информация
        self.print_step("Получаване на информация...")
        info = self.get_video_info(url)
        
        if info:
            duration_min = info.get('duration', 0) / 60
            self.print_info(f"Заглавие: {info.get('title', 'N/A')[:60]}")
            self.print_info(f"Продължителност: {duration_min:.1f} минути")
            self.print_info(f"Качване: {info.get('upload_date', 'N/A')}")
            print("")
        
        # Конфигурация за сваляне
        ydl_opts = {
            'format': 'bestaudio/best',
            'postprocessors': [{
                'key': 'FFmpegExtractAudio',
                'preferredcodec': 'mp3',
                'preferredquality': quality,
            }],
            'outtmpl': str(Path(output_dir) / '%(title)s.%(ext)s'),
            'quiet': False,
            'no_warnings': False,
            'progress_hooks': [self.progress_hook],
        }
        
        # Добавяне на thumbnail и metadata ако са включени
        if self.config.get('embed_thumbnail'):
            ydl_opts['postprocessors'].append({
                'key': 'EmbedThumbnail',
                'already_have_thumbnail': False,
            })
        
        if self.config.get('add_metadata'):
            ydl_opts['postprocessors'].append({
                'key': 'FFmpegMetadata',
            })
        
        try:
            self.print_step("Стартиране на сваляне...")
            self.print_step(f"Качество: {quality} kbps")
            print(f"\n  {Colors.DIM}─" * 50 + f"{Colors.RESET}")
            
            with yt_dlp.YoutubeDL(ydl_opts) as ydl:
                ydl.download([url])
            
            print(f"\n  {Colors.DIM}─" * 50 + f"{Colors.RESET}")
            self.print_ok(f"Готово! Файлът е записан в: {output_dir}")
            
            # Въпрос за отваряне на папката
            open_folder = input("\n  Да се отвори ли папката? (д/н) [н]: ").strip().lower()
            if open_folder == 'д':
                if platform.system() == 'Windows':
                    os.startfile(output_dir)
                else:
                    subprocess.run(['open', output_dir] if platform.system() == 'Darwin' else ['xdg-open', output_dir])
        
        except Exception as e:
            self.print_error(f"Грешка при сваляне: {str(e)}")
    
    def download_playlist(self, url: str, quality: str, output_dir: str):
        """Сваляне на плейлист"""
        # Създаване на папка за плейлиста
        playlist_dir = Path(output_dir) / "Playlists"
        playlist_dir.mkdir(parents=True, exist_ok=True)
        
        # Показване на информация за плейлиста
        self.print_step("Получаване на информация за плейлиста...")
        info = self.get_video_info(url)
        
        if info and 'entries' in info:
            self.print_info(f"Намерени {len(info['entries'])} видеа в плейлиста")
            print("")
        
        ydl_opts = {
            'format': 'bestaudio/best',
            'postprocessors': [{
                'key': 'FFmpegExtractAudio',
                'preferredcodec': 'mp3',
                'preferredquality': quality,
            }],
            'outtmpl': str(playlist_dir / '%(playlist_index)s - %(title)s.%(ext)s'),
            'quiet': False,
            'no_warnings': False,
            'ignoreerrors': True,
            'extract_flat': False,
        }
        
        # Добавяне на thumbnail и metadata
        if self.config.get('embed_thumbnail'):
            ydl_opts['postprocessors'].append({
                'key': 'EmbedThumbnail',
                'already_have_thumbnail': False,
            })
        
        if self.config.get('add_metadata'):
            ydl_opts['postprocessors'].append({
                'key': 'FFmpegMetadata',
            })
        
        try:
            self.print_step("Стартиране на сваляне на плейлиста...")
            print(f"\n  {Colors.DIM}─" * 50 + f"{Colors.RESET}")
            
            with yt_dlp.YoutubeDL(ydl_opts) as ydl:
                ydl.download([url])
            
            print(f"\n  {Colors.DIM}─" * 50 + f"{Colors.RESET}")
            self.print_ok(f"Плейлистът е свален успешно в: {playlist_dir}")
        
        except Exception as e:
            self.print_error(f"Грешка при сваляне на плейлист: {str(e)}")
    
    def progress_hook(self, d):
        """Hook за показване на прогреса"""
        if d['status'] == 'downloading':
            if 'total_bytes' in d:
                percent = d['downloaded_bytes'] / d['total_bytes'] * 100
                print(f"\r  {Colors.CYAN}Сваляне: {percent:.1f}%{Colors.RESET}", end='')
        elif d['status'] == 'finished':
            print(f"\r  {Colors.GREEN}Обработка на аудио...{Colors.RESET}")
    
    def update_ytdlp(self):
        """Обновяване на yt-dlp"""
        self.print_step("Обновяване на yt-dlp...")
        try:
            subprocess.run([sys.executable, '-m', 'pip', 'install', '--upgrade', 'yt-dlp'])
            self.print_ok("yt-dlp е обновен успешно!")
        except Exception as e:
            self.print_error(f"Грешка при обновяване: {str(e)}")
    
    def clear_cache(self):
        """Изчистване на кеша"""
        self.print_step("Изчистване на кеша...")
        cache_dir = Path.home() / ".cache" / "yt-dlp"
        if cache_dir.exists():
            import shutil
            shutil.rmtree(cache_dir)
            self.print_ok("Кешът е изчистен!")
        else:
            self.print_info("Няма намерен кеш")
    
    def show_stats(self):
        """Показване на статистика"""
        print(f"\n  {Colors.BOLD}📊 Статистика:{Colors.RESET}")
        print(f"    Качество: {self.config.get('quality')} kbps")
        print(f"    Папка: {self.config.get('output_dir')}")
        print(f"    Thumbnail: {'Да' if self.config.get('embed_thumbnail') else 'Не'}")
        print(f"    Metadata: {'Да' if self.config.get('add_metadata') else 'Не'}")
        print(f"    Последна промяна: {self.config.get('last_updated')}")
        
        # Броене на MP3 файловете
        output_dir = Path(self.config.get('output_dir'))
        if output_dir.exists():
            mp3_files = list(output_dir.rglob("*.mp3"))
            print(f"    Общо MP3 файлове: {len(mp3_files)}")
    
    def toggle_setting(self, setting: str):
        """Превключване на настройка"""
        current = self.config.get(setting)
        self.config.set(setting, not current)
        self.print_ok(f"{setting} е {'включена' if not current else 'изключена'}")

# ========== MAIN MENU ==========

def main():
    """Основно меню"""
    downloader = YouTubeDownloader()
    
    while True:
        downloader.print_header()
        
        print(f"  {Colors.DIM}═" * 50 + f"{Colors.RESET}")
        print(f"  {Colors.BOLD}МЕНЮ{Colors.RESET}")
        print(f"  {Colors.DIM}─" * 50 + f"{Colors.RESET}")
        print(f"    {Colors.CYAN}1){Colors.RESET} Свали едно видео като MP3")
        print(f"    {Colors.CYAN}2){Colors.RESET} Свали целия плейлист")
        print(f"    {Colors.CYAN}3){Colors.RESET} Само аудио (без thumbnail)")
        print(f"  {Colors.DIM}─" * 50 + f"{Colors.RESET}")
        print(f"    {Colors.YELLOW}4){Colors.RESET} Обнови yt-dlp")
        print(f"    {Colors.YELLOW}5){Colors.RESET} Настройки")
        print(f"    {Colors.YELLOW}6){Colors.RESET} Статистика")
        print(f"    {Colors.YELLOW}7){Colors.RESET} Изчисти кеша")
        print(f"  {Colors.DIM}─" * 50 + f"{Colors.RESET}")
        print(f"    {Colors.RED}0){Colors.RESET} Изход")
        print(f"  {Colors.DIM}═" * 50 + f"{Colors.RESET}")
        
        choice = input("\n  Избор [0-7]: ").strip()
        
        if choice == "1":
            print("")
            url = input("  YouTube URL: ").strip()
            if not url:
                downloader.print_warning("Не е въведен URL.")
                input("\n  Натисни Enter за продължение...")
                continue
            
            quality = downloader.select_quality()
            output_dir = downloader.select_output_dir()
            downloader.download_single(url, quality, output_dir)
            input("\n  Натисни Enter за менюто...")
        
        elif choice == "2":
            print("")
            url = input("  YouTube плейлист URL: ").strip()
            if not url:
                downloader.print_warning("Не е въведен URL.")
                input("\n  Натисни Enter за продължение...")
                continue
            
            quality = downloader.select_quality()
            output_dir = downloader.select_output_dir()
            downloader.download_playlist(url, quality, output_dir)
            input("\n  Натисни Enter за менюто...")
        
        elif choice == "3":
            print("")
            url = input("  YouTube URL: ").strip()
            if not url:
                downloader.print_warning("Не е въведен URL.")
                input("\n  Натисни Enter за продължение...")
                continue
            
            quality = downloader.select_quality()
            output_dir = downloader.select_output_dir()
            
            # Временно изключване на thumbnail и metadata
            old_thumb = downloader.config.get('embed_thumbnail')
            old_meta = downloader.config.get('add_metadata')
            downloader.config.set('embed_thumbnail', False)
            downloader.config.set('add_metadata', False)
            
            downloader.download_single(url, quality, output_dir)
            
            # Възстановяване
            downloader.config.set('embed_thumbnail', old_thumb)
            downloader.config.set('add_metadata', old_meta)
            input("\n  Натисни Enter за менюто...")
        
        elif choice == "4":
            downloader.update_ytdlp()
            input("\n  Натисни Enter за менюто...")
        
        elif choice == "5":
            while True:
                downloader.print_header()
                print(f"  {Colors.BOLD}⚙️  НАСТРОЙКИ{Colors.RESET}\n")
                print(f"    1) Качество: {downloader.config.get('quality')} kbps")
                print(f"    2) Папка: {downloader.config.get('output_dir')}")
                print(f"    3) Thumbnail: {'✓' if downloader.config.get('embed_thumbnail') else '✗'}")
                print(f"    4) Metadata: {'✓' if downloader.config.get('add_metadata') else '✗'}")
                print(f"  {Colors.DIM}─" * 50 + f"{Colors.RESET}")
                print(f"    0) Назад")
                
                sub_choice = input("\n  Избор [0-4]: ").strip()
                
                if sub_choice == "1":
                    downloader.select_quality()
                elif sub_choice == "2":
                    downloader.select_output_dir()
                elif sub_choice == "3":
                    downloader.toggle_setting('embed_thumbnail')
                elif sub_choice == "4":
                    downloader.toggle_setting('add_metadata')
                elif sub_choice == "0":
                    break
                else:
                    downloader.print_warning("Невалиден избор!")
        
        elif choice == "6":
            downloader.show_stats()
            input("\n  Натисни Enter за менюто...")
        
        elif choice == "7":
            downloader.clear_cache()
            input("\n  Натисни Enter за менюто...")
        
        elif choice == "0":
            print("")
            downloader.print_info("Довиждане!")
            print("")
            sys.exit(0)
        
        else:
            downloader.print_warning("Невалиден избор. Моля въведи 0-7.")
            input("\n  Натисни Enter за продължение...")

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print(f"\n\n  {Colors.YELLOW}⚠ Програмата е спряна от потребителя{Colors.RESET}")
        sys.exit(0)
    except Exception as e:
        print(f"\n\n  {Colors.RED}❌ Критична грешка: {str(e)}{Colors.RESET}")
        input("\n  Натисни Enter за изход...")
        sys.exit(1)