#!/usr/bin/env python3
"""
╔══════════════════════════════════════════════════════════╗
║                   Fedya-MP3 Downloader           ║
║           YouTube & Facebook  →  MP3 Downloader          ║
║                    github.com/you                        ║
╚══════════════════════════════════════════════════════════╝

Изисквания:
    pip install yt-dlp colorama
    + FFmpeg инсталиран (или ffmpeg.exe в същата папка)
"""

import os
import sys
import re
import subprocess
import time
from pathlib import Path
from datetime import datetime

# ── Colorama (Windows ANSI support) ──────────────────────────────────────────
try:
    from colorama import just_fix_windows_console
    just_fix_windows_console()
except ImportError:
    pass  # не е критично

# ── Цветова палитра ───────────────────────────────────────────────────────────
class C:
    RESET   = "\033[0m"
    BOLD    = "\033[1m"
    DIM     = "\033[2m"
    # основни
    WHITE   = "\033[97m"
    CYAN    = "\033[96m"
    BLUE    = "\033[94m"
    GREEN   = "\033[92m"
    YELLOW  = "\033[93m"
    RED     = "\033[91m"
    MAGENTA = "\033[95m"
    GRAY    = "\033[90m"
    # акцентни
    BG_DARK = "\033[40m"

def c(*codes):
    return "".join(codes)

def paint(text, *codes):
    return c(*codes) + str(text) + C.RESET


# ── Банер ─────────────────────────────────────────────────────────────────────
BANNER = f"""
{c(C.CYAN, C.BOLD)}
  ______ ______ _______     __        __  __ _____ ____  
 |  ____|  ____|  __ \ \   / //\     |  \/  |  __ \___ \ 
 | |__  | |__  | |  | \ \_/ //  \    | \  / | |__) |__) |
 |  __| |  __| | |  | |\   // /\ \   | |\/| |  ___/|__ < 
 | |    | |____| |__| | | |/ ____ \  | |  | | |    ___) |
 |_|    |______|_____/  |_/_/    \_\ |_|  |_|_|   |____/ 
                                                         
                                                         

 {C.RESET}{c(C.BLUE, C.BOLD)}
  ──────────────────────────────────────────────────────────
          YouTube & Facebook  →  MP3  Downloader
  ──────────────────────────────────────────────────────────
{C.RESET}"""

def banner():
    os.system("cls" if sys.platform == "win32" else "clear")
    print(BANNER)


# ── Проверка / инсталация на зависимости ─────────────────────────────────────
def ensure_dependencies():
    missing = []
    try:
        import yt_dlp  # noqa
    except ImportError:
        missing.append("yt-dlp")
    try:
        import colorama  # noqa
    except ImportError:
        missing.append("colorama")

    if missing:
        print(paint(f"  ⚙  Инсталирам: {', '.join(missing)} …", C.YELLOW))
        subprocess.run(
            [sys.executable, "-m", "pip", "install", "--quiet"] + missing,
            check=True
        )
        print(paint("  ✓  Готово.\n", C.GREEN))


# ── Проверка за нова версия на yt-dlp ────────────────────────────────────────
def check_ytdlp_update():
    """
    Сравнява инсталираната версия на yt-dlp с последната в PyPI.
    Ако има по-нова – пита потребителя дали да обнови.
    Работи в отделна нишка, за да не бави стартирането.
    """
    import threading

    def _check():
        try:
            import urllib.request
            import json
            import yt_dlp

            installed = yt_dlp.version.__version__

            url = "https://pypi.org/pypi/yt-dlp/json"
            req = urllib.request.Request(url, headers={"User-Agent": "MediaSnatch/1.0"})
            with urllib.request.urlopen(req, timeout=5) as resp:
                data = json.loads(resp.read())
            latest = data["info"]["version"]

            def parse_ver(v):
                """2026.03.17  →  (2026, 3, 17)  за коректно сравнение"""
                try:
                    return tuple(int(x) for x in v.split("."))
                except ValueError:
                    return (0,)

            if parse_ver(installed) >= parse_ver(latest):
                print(
                    f"  {paint('✓', C.GREEN)}  yt-dlp {paint(installed, C.CYAN)}  "
                    f"{paint('– актуална версия', C.GRAY)}"
                )
            else:
                print(
                    f"\n  {paint('⚠  Налична е нова версия на yt-dlp!', C.YELLOW, C.BOLD)}\n"
                    f"     Инсталирана : {paint(installed, C.RED)}\n"
                    f"     Нова        : {paint(latest, C.GREEN, C.BOLD)}\n"
                )
                ans = input(
                    paint("  Обновявам сега? [Y/n]: ", C.BOLD)
                ).strip().lower()

                if ans in {"", "y", "yes", "да"}:
                    print(paint("  ⚙  Обновявам yt-dlp…", C.YELLOW))
                    result = subprocess.run(
                        [sys.executable, "-m", "pip", "install", "--upgrade", "--quiet", "yt-dlp"],
                        capture_output=True, text=True
                    )
                    if result.returncode == 0:
                        print(paint(f"  ✓  yt-dlp обновен до {latest}\n", C.GREEN, C.BOLD))
                    else:
                        print(paint("  ✗  Обновяването неуспешно. Опитай ръчно: pip install -U yt-dlp\n", C.RED))
                else:
                    print(paint(
                        f"  ℹ  Пропускам. Ако сваляниeто гърми, пусни:\n"
                        f"     pip install -U yt-dlp\n",
                        C.GRAY
                    ))

        except Exception:
            # Мълчим при грешка – няма интернет или PyPI timeout; не е критично
            pass

    t = threading.Thread(target=_check, daemon=True)
    t.start()
    t.join(timeout=7)   # изчакваме макс. 7 секунди; после продължаваме


# ── FFmpeg локация ────────────────────────────────────────────────────────────
def find_ffmpeg() -> str | None:
    """Търси ffmpeg: в PATH, до скрипта/exe-то."""
    import shutil

    # 1. В PATH
    if shutil.which("ffmpeg"):
        return None  # yt-dlp ще го намери сам

    # 2. До .exe (PyInstaller _MEIPASS) или до .py
    base = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
    candidate = os.path.join(base, "ffmpeg.exe" if sys.platform == "win32" else "ffmpeg")
    if os.path.isfile(candidate):
        return candidate

    return "NOT_FOUND"


# ── Десктоп ───────────────────────────────────────────────────────────────────
def get_desktop() -> Path:
    if sys.platform == "win32":
        desktop = Path(os.environ.get("USERPROFILE", Path.home())) / "Desktop"
    else:
        desktop = Path.home() / "Desktop"

    if not desktop.exists():
        desktop = Path.home() / "Downloads"
        desktop.mkdir(exist_ok=True)

    # Подпапка Fedya-MP3
    folder = desktop / "Fedya-MP3"
    folder.mkdir(exist_ok=True)
    return folder


# ── URL разпознаване ──────────────────────────────────────────────────────────
YT_RE = re.compile(
    r"https?://(www\.)?(youtube\.com|youtu\.be|music\.youtube\.com)/.+"
)
FB_RE = re.compile(
    r"https?://(www\.|m\.|web\.)?facebook\.com/.+|https?://fb\.watch/.+"
)

def detect_source(url: str) -> str:
    url = url.strip()
    if YT_RE.match(url):  return "YouTube"
    if FB_RE.match(url):  return "Facebook"
    return "Unknown"


# ── Progress hook ─────────────────────────────────────────────────────────────
_last_pct = -1

def progress_hook(d):
    global _last_pct
    status = d.get("status")

    if status == "downloading":
        pct_raw = d.get("_percent_str", "").strip().replace("%", "")
        try:
            pct = int(float(pct_raw))
        except (ValueError, TypeError):
            pct = 0

        if pct == _last_pct:
            return
        _last_pct = pct

        filled = int(pct / 5)   # 20-char bar
        bar = paint("█" * filled, C.CYAN) + paint("░" * (20 - filled), C.GRAY)

        speed  = d.get("_speed_str",  "?").strip()
        eta    = d.get("_eta_str",    "?").strip()

        line = (
            f"\r  {bar} "
            f"{paint(f'{pct:3d}%', C.BOLD, C.WHITE)}  "
            f"{paint(speed, C.YELLOW)}  "
            f"ETA {paint(eta, C.BLUE)}"
        )
        print(line, end="", flush=True)

    elif status == "finished":
        print(f"\r  {paint('█'*20, C.GREEN)} {paint('100%  ✓ конвертирам в MP3…', C.GREEN, C.BOLD)}")
        _last_pct = -1


# ── Изтегляне ─────────────────────────────────────────────────────────────────
def download(url: str, output_dir: Path, source: str, quality: str) -> bool:
    import yt_dlp

    global _last_pct
    _last_pct = -1

    output_template = str(output_dir / "%(title).80s.%(ext)s")

    ffmpeg_loc = find_ffmpeg()
    if ffmpeg_loc == "NOT_FOUND":
        print(paint(
            "\n  ✗  FFmpeg не е намерен!\n"
            "     Windows: winget install ffmpeg\n"
            "     macOS  : brew install ffmpeg\n"
            "     Linux  : sudo apt install ffmpeg\n"
            "     ИЛИ сложи ffmpeg.exe до програмата.\n",
            C.RED
        ))
        return False

    ydl_opts = {
        "format"        : "bestaudio/best",
        "postprocessors": [{
            "key"             : "FFmpegExtractAudio",
            "preferredcodec"  : "mp3",
            "preferredquality": quality,
        }],
        "outtmpl"         : output_template,
        "quiet"           : True,
        "no_warnings"     : True,
        "noplaylist"      : True,
        "socket_timeout"  : 30,
        "progress_hooks"  : [progress_hook],
        "postprocessor_hooks": [],
    }

    if ffmpeg_loc:  # намерен до exe-то
        ydl_opts["ffmpeg_location"] = os.path.dirname(ffmpeg_loc)

    # Facebook лични видеа – cookies от Chrome
    if source == "Facebook":
        try:
            ydl_opts["cookiesfrombrowser"] = ("chrome",)
        except Exception:
            pass

    icon_yt = paint("▶", C.RED, C.BOLD)
    icon_fb = paint("f", C.BLUE, C.BOLD)
    icon    = icon_yt if source == "YouTube" else icon_fb

    print(f"\n  {icon}  {paint(source, C.BOLD)}  {paint(url[:72] + ('…' if len(url)>72 else ''), C.GRAY)}")
    print(f"  {paint('Изтеглям…', C.DIM)}")

    t_start = time.time()

    try:
        with yt_dlp.YoutubeDL(ydl_opts) as ydl:
            info  = ydl.extract_info(url, download=True)
            title = info.get("title", "Unknown")

        elapsed = time.time() - t_start
        print(
            f"\n  {paint('✓', C.GREEN, C.BOLD)}  "
            f"{paint(title[:60], C.WHITE, C.BOLD)}\n"
            f"     {paint(f'Качество: {quality} kbps  |  Времетраене: {elapsed:.1f}s', C.GRAY)}"
        )
        return True

    except yt_dlp.utils.DownloadError as e:
        msg = str(e).lower()
        print(f"\n  {paint('✗  Грешка:', C.RED, C.BOLD)}", str(e)[:120])
        if "login" in msg or "private" in msg or "sign in" in msg:
            print(paint(
                "     💡 Видеото изисква вход. За Facebook добавете cookies от Chrome.\n"
                "        За YouTube: youtube.com/premium или публично видео.",
                C.YELLOW
            ))
        return False

    except Exception as e:
        print(f"\n  {paint('✗  Неочаквана грешка:', C.RED)} {e}")
        return False


# ── Меню за качество ──────────────────────────────────────────────────────────
QUALITY_MAP = {
    "1": ("320", "320 kbps  – Максимално (по-голям файл)"),
    "2": ("192", "192 kbps  – Отлично     (препоръчително)"),
    "3": ("128", "128 kbps  – Добро        (по-малък файл)"),
    "4": ("64",  " 64 kbps  – Ниско        (podcasts/говор)"),
}

def choose_quality() -> str:
    print(f"\n  {paint('Качество на MP3:', C.CYAN, C.BOLD)}")
    for k, (q, label) in QUALITY_MAP.items():
        print(f"    {paint(k, C.YELLOW, C.BOLD)}.  {label}")
    while True:
        choice = input(paint("\n  Избор [1-4, Enter=2]: ", C.BOLD)).strip() or "2"
        if choice in QUALITY_MAP:
            return QUALITY_MAP[choice][0]


# ── История ───────────────────────────────────────────────────────────────────
def log_history(output_dir: Path, url: str, title: str, source: str, quality: str):
    log_path = output_dir / "download_history.txt"
    now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    with open(log_path, "a", encoding="utf-8") as f:
        f.write(f"[{now}] [{source}] [{quality}kbps] {title}\n  {url}\n\n")


# ── Разделители ───────────────────────────────────────────────────────────────
def divider(char="─", color=C.GRAY):
    try:
        w = os.get_terminal_size().columns
    except Exception:
        w = 72
    print(paint(char * min(w, 72), color))


def section(title: str):
    divider("═", C.BLUE)
    print(f"  {paint(title, C.CYAN, C.BOLD)}")
    divider("─")


# ── Main ──────────────────────────────────────────────────────────────────────
def main():
    ensure_dependencies()
    banner()

    # Проверка за нова версия на yt-dlp (при всяко стартиране)
    print(paint("  Проверявам версията на yt-dlp…", C.GRAY))
    check_ytdlp_update()

    output_dir = get_desktop()
    print(
        f"  {paint('📁', '')} Файловете се запазват в:  "
        f"{paint(str(output_dir), C.CYAN, C.BOLD)}\n"
    )

    quality = choose_quality()
    success = 0
    failed  = 0

    section("ВМЪКНИ URL  (q = изход, c = смени качество)")

    while True:
        print()
        try:
            raw = input(paint("  URL ▸ ", C.BOLD, C.WHITE)).strip()
        except (KeyboardInterrupt, EOFError):
            raw = "q"

        if not raw:
            continue

        if raw.lower() in {"q", "quit", "exit", "изход"}:
            break

        if raw.lower() in {"c", "quality", "качество"}:
            quality = choose_quality()
            section("ВМЪКНИ URL  (q = изход, c = смени качество)")
            continue

        source = detect_source(raw)
        if source == "Unknown":
            print(paint("  ⚠  Не е разпознат YouTube или Facebook URL.", C.YELLOW))
            continue

        ok = download(raw, output_dir, source, quality)
        if ok:
            success += 1
        else:
            failed += 1

        divider("─", C.GRAY)
        status = (
            paint(f"✓ {success}", C.GREEN, C.BOLD) +
            paint("  ✗ ", C.GRAY) +
            paint(str(failed), C.RED if failed else C.GRAY)
        )
        print(f"  Сесия: {status}  │  "
              f"{paint('c', C.YELLOW)} = качество   "
              f"{paint('q', C.YELLOW)} = изход")

    # Край
    divider("═", C.BLUE)
    print(
        f"\n  {paint('Сесията приключи.', C.CYAN, C.BOLD)}\n"
        f"  Свалени: {paint(success, C.GREEN, C.BOLD)}   "
        f"Неуспешни: {paint(failed, C.RED if failed else C.GRAY, C.BOLD)}\n"
        f"  Папка: {paint(str(output_dir), C.CYAN)}\n"
    )

    if sys.platform == "win32":
        input(paint("  Натисни Enter за изход…", C.GRAY))


if __name__ == "__main__":
    main()


# ═══════════════════════════════════════════════════════════════════════════════
# КОМПИЛИРАНЕ В .EXE  (с PyInstaller)
# ═══════════════════════════════════════════════════════════════════════════════
#
#  1. Инсталирай зависимостите:
#       pip install yt-dlp colorama pyinstaller
#
#  2. Свали ffmpeg.exe и го сложи в СЪЩАТА папка като mediasnatch.py
#     (от https://ffmpeg.org/download.html → Windows Builds)
#
#  3. Компилирай:
#       pyinstaller --onefile --console --icon=downloader.ico ^
#         --add-binary "ffmpeg.exe;." ^
#         --name MediaSnatch ^
#         mediasnatch.py
#
#  4. Готовият .exe е в папка dist\MediaSnatch.exe
#
#  ЗАБЕЛЕЖКА: --add-binary синтаксис на Linux/macOS е с ":" вместо ";"
#       pyinstaller --onefile --console --icon=downloader.ico \
#         --add-binary "ffmpeg:." \
#         --name MediaSnatch \
#         mediasnatch.py
# ═══════════════════════════════════════════════════════════════════════════════
