"""
YouTube Downloader Module for VideoDetection.
Hỗ trợ tải video hoặc 1 đoạn chỉ định (mặc định 1h đầu) từ YouTube:
- Hỗ trợ ID 11 ký tự, Full URL, URL rút gọn youtu.be, Shorts, Embed, Live.
- Chỉ tải đúng phân đoạn dữ liệu cần thiết (Range request) qua yt-dlp và ffmpeg.
- Mặc định lưu vào Target/, tùy chọn lưu vào Data/ hoặc thư mục chỉ định.
- Hỗ trợ báo cáo tiến độ thời gian thực cho cả CLI và GUI.
"""
from __future__ import annotations

from dataclasses import dataclass
import logging
from pathlib import Path
import re
from typing import Callable, Optional, Tuple
from urllib.parse import parse_qs, urlparse

import yt_dlp
import yt_dlp.utils as ytdlp_utils

from ..core.paths import DATA_DIR, TARGET_DIR, get_ffmpeg_path

logger = logging.getLogger(__name__)

# Phân đoạn mặc định: 1 giờ đầu tiên của video
DEFAULT_SECTION_1H = "*00:00:00-01:00:00"

# Regex nhận diện Video ID chuẩn của YouTube (11 ký tự)
YOUTUBE_ID_REGEX = re.compile(r"^[a-zA-Z0-9_-]{11}$")


@dataclass
class YouTubeDownloadResult:
    """Kết quả tải video YouTube."""
    success: bool
    file_path: Optional[Path] = None
    video_id: str = ""
    title: str = ""
    duration_sec: float = 0.0
    section: Optional[str] = None
    dest_dir: Optional[Path] = None
    error_message: Optional[str] = None


def extract_youtube_id(url_or_id: str) -> str:
    """
    Trích xuất YouTube video ID (11 ký tự) từ chuỗi nhập vào:
    Hỗ trợ: ID thô, link đầy đủ youtube.com, rút gọn youtu.be, shorts, embed, live.
    """
    if not url_or_id:
        raise ValueError("URL hoặc Video ID không được để trống.")

    cleaned = url_or_id.strip()

    # 1. Nếu người dùng nhập trực tiếp 11 ký tự ID
    if YOUTUBE_ID_REGEX.match(cleaned):
        return cleaned

    # Bổ sung scheme nếu thiếu để urlparse hoạt động chuẩn
    if not cleaned.startswith(("http://", "https://")):
        cleaned = "https://" + cleaned

    parsed = urlparse(cleaned)
    hostname = (parsed.hostname or "").lower()

    # 2. Link rút gọn youtu.be/<id>
    if "youtu.be" in hostname:
        path_parts = [p for p in parsed.path.split("/") if p]
        if path_parts and YOUTUBE_ID_REGEX.match(path_parts[0]):
            return path_parts[0]

    # 3. youtube.com
    if "youtube.com" in hostname or "youtube-nocookie.com" in hostname:
        # Đường dẫn /watch?v=<id>
        if parsed.path == "/watch" or parsed.path.startswith("/watch/"):
            qs = parse_qs(parsed.query)
            if "v" in qs and qs["v"]:
                vid = qs["v"][0]
                if YOUTUBE_ID_REGEX.match(vid):
                    return vid

        # Đường dẫn /embed/<id>, /shorts/<id>, /live/<id>, /v/<id>
        path_parts = [p for p in parsed.path.split("/") if p]
        if len(path_parts) >= 2 and path_parts[0] in ("embed", "shorts", "live", "v"):
            vid = path_parts[1]
            if YOUTUBE_ID_REGEX.match(vid):
                return vid

    # 4. Fallback Regex tìm chuỗi 11 ký tự sau v= hoặc /
    fallback_match = re.search(r"(?:v=|\/)([a-zA-Z0-9_-]{11})(?:[&?]|$)", url_or_id)
    if fallback_match:
        return fallback_match.group(1)

    raise ValueError(f"Không thể nhận diện YouTube Video ID hoặc URL hợp lệ từ: '{url_or_id}'")


def normalize_youtube_url(url_or_id: str) -> str:
    """Chuẩn hóa thành URL xem trực tiếp chuẩn: https://www.youtube.com/watch?v=<ID>"""
    vid = extract_youtube_id(url_or_id)
    return f"https://www.youtube.com/watch?v={vid}"


def _parse_time_token_to_sec(token: str) -> float:
    """Chuyển đổi token thời gian (giây hoặc HH:MM:SS) thành số giây."""
    token = token.strip().lstrip("*")
    if token.lower() in ("inf", "infinity", "end", "het"):
        return float("inf")
    if token.isdigit():
        return float(token)

    dur = ytdlp_utils.parse_duration(token)
    return float(dur) if dur is not None else 0.0


def parse_section_range(section: Optional[str]) -> Optional[str]:
    """
    Chuẩn hóa chuỗi section:
    - None / "all" / "full" / "toan_bo" -> None (tải toàn bộ video)
    - "" / "default" / "1h" / "1h_dau" -> "*00:00:00-01:00:00" (1h đầu)
    - "01:30 - 05:00" -> "*01:30-05:00"
    """
    if section is None:
        return None

    sec_str = section.strip()
    if sec_str.lower() in ("all", "full", "toan_bo", "none"):
        return None

    if not sec_str or sec_str.lower() in ("default", "1h", "1h_dau"):
        return DEFAULT_SECTION_1H

    # Loại bỏ khoảng trắng xung quanh dấu '-'
    parts = [p.strip() for p in sec_str.lstrip("*").split("-")]
    if len(parts) == 2:
        return f"*{parts[0]}-{parts[1]}"

    if not sec_str.startswith("*"):
        return f"*{sec_str}"
    return sec_str


def parse_range_to_seconds(section: Optional[str]) -> Optional[Tuple[float, float]]:
    """Phân tích chuỗi section thành cặp tuple (start_sec, end_sec)."""
    norm_section = parse_section_range(section)
    if norm_section is None:
        return None

    cleaned = norm_section.lstrip("*")
    parts = cleaned.split("-")
    if len(parts) != 2:
        return (0.0, 3600.0)

    start = _parse_time_token_to_sec(parts[0])
    end = _parse_time_token_to_sec(parts[1])
    return (start, end)


def resolve_dest_dir(dest: Optional[str | Path]) -> Path:
    """
    Xác định thư mục đích:
    - 'target' / 'Target' / None -> TARGET_DIR
    - 'data' / 'Data' -> DATA_DIR
    - Đường dẫn tùy biến Path/str -> resolve và tạo thư mục nếu chưa có.
    """
    if dest is None:
        out_dir = TARGET_DIR
    elif isinstance(dest, str):
        lower_dest = dest.strip().lower()
        if lower_dest == "target":
            out_dir = TARGET_DIR
        elif lower_dest == "data":
            out_dir = DATA_DIR
        else:
            out_dir = Path(dest).resolve()
    else:
        out_dir = Path(dest).resolve()

    out_dir.mkdir(parents=True, exist_ok=True)
    return out_dir


def build_ytdlp_options(
    dest_dir: Path,
    section: Optional[str] = None,
    progress_callback: Optional[Callable[[int, str], None]] = None,
) -> dict:
    """
    Xây dựng cấu hình YoutubeDL tối ưu cho VideoDetection:
    - Định dạng MP4 chuẩn với H.264/AAC.
    - Cắt section chính xác không giật khung hình (--force-keyframes-at-cuts).
    - Tận dụng Node.js runtime cho JavaScript engine của YouTube.
    """
    sec_range = parse_range_to_seconds(section)

    outtmpl = str(dest_dir / "%(title).100B [%(id)s].%(ext)s")

    opts: dict = {
        "quiet": True,
        "no_warnings": True,
        "format": "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best",
        "merge_output_format": "mp4",
        "outtmpl": {"default": outtmpl},
        "windowsfilenames": True,
        "restrictfilenames": False,
        "force_keyframes_at_cuts": True,
        "js_runtimes": {"node": {}},
    }

    # Bổ sung đường dẫn ffmpeg nếu có
    ffmpeg_exe = get_ffmpeg_path()
    if ffmpeg_exe and Path(ffmpeg_exe).exists():
        opts["ffmpeg_location"] = str(Path(ffmpeg_exe).parent)

    # Cấu hình download section nếu có
    if sec_range is not None:
        opts["download_ranges"] = ytdlp_utils.download_range_func(None, [sec_range])

    # Hook báo cáo tiến độ
    if progress_callback:
        def progress_hook(d: dict):
            status = d.get("status")
            if status == "downloading":
                total = d.get("total_bytes") or d.get("total_bytes_estimate") or 0
                downloaded = d.get("downloaded_bytes") or 0
                pct = int((downloaded / total * 90)) if total > 0 else 50
                speed = d.get("_speed_str", "").strip()
                eta = d.get("_eta_str", "").strip()
                speed_str = f" | Tốc độ: {speed}" if speed else ""
                eta_str = f" | Còn lại: {eta}" if eta else ""
                progress_callback(min(90, max(5, pct)), f"Đang tải video...{speed_str}{eta_str}")
            elif status == "finished":
                progress_callback(95, "Đang ghép nối và đóng gói MP4...")

        opts["progress_hooks"] = [progress_hook]

    return opts


def download_youtube_video(
    url_or_id: str,
    section: Optional[str] = DEFAULT_SECTION_1H,
    dest: Optional[str | Path] = "target",
    progress_callback: Optional[Callable[[int, str], None]] = None,
) -> YouTubeDownloadResult:
    """
    Tải video hoặc một đoạn chỉ định từ YouTube vào thư mục đích:
    - url_or_id: ID 11 ký tự hoặc link YouTube bất kỳ.
    - section: Đoạn cần tải (Mặc định 1h đầu: '*00:00:00-01:00:00', hoặc 'all' để tải hết).
    - dest: 'target' (mặc định) hoặc 'data' hoặc đường dẫn Path tùy chọn.
    - progress_callback: Hàm nhận (tiến độ %, thông điệp).
    """
    video_id = ""
    try:
        video_id = extract_youtube_id(url_or_id)
        canonical_url = normalize_youtube_url(video_id)
        target_dir = resolve_dest_dir(dest)
        norm_section = parse_section_range(section)

        if progress_callback:
            sec_display = "1 Giờ đầu" if norm_section == DEFAULT_SECTION_1H else (norm_section or "Toàn bộ")
            progress_callback(5, f"Đang kết nối YouTube (ID: {video_id}, Phân đoạn: {sec_display})...")

        ydl_opts = build_ytdlp_options(
            dest_dir=target_dir,
            section=norm_section,
            progress_callback=progress_callback,
        )

        with yt_dlp.YoutubeDL(ydl_opts) as ydl:
            info = ydl.extract_info(canonical_url, download=True)
            if not info:
                raise RuntimeError(f"Không thể lấy thông tin video từ: {canonical_url}")

            title = info.get("title", f"YouTube_{video_id}")
            dur = float(info.get("duration", 0.0))

            # Xác định file kết quả sau khi merge MP4
            raw_filename = ydl.prepare_filename(info)
            final_path = Path(raw_filename).with_suffix(".mp4")

            # Fallback nếu tên file khác đuôi
            if not final_path.exists():
                candidate = Path(raw_filename)
                if candidate.exists():
                    final_path = candidate
                else:
                    # Tìm file gần nhất có chứa video_id trong target_dir
                    matched_files = list(target_dir.glob(f"*{video_id}*.mp4"))
                    if matched_files:
                        final_path = max(matched_files, key=lambda p: p.stat().st_mtime)

            if not final_path.exists():
                raise FileNotFoundError(f"Không tìm thấy file video đầu ra sau khi tải: {final_path}")

            if progress_callback:
                progress_callback(100, f"Tải hoàn tất: {final_path.name}")

            return YouTubeDownloadResult(
                success=True,
                file_path=final_path,
                video_id=video_id,
                title=title,
                duration_sec=dur,
                section=norm_section,
                dest_dir=target_dir,
            )

    except Exception as e:
        logger.error(f"Lỗi khi tải video YouTube: {e}", exc_info=True)
        if progress_callback:
            progress_callback(0, f"Lỗi tải video: {e}")
        return YouTubeDownloadResult(
            success=False,
            video_id=video_id,
            error_message=str(e),
        )
