import os
import time
import random
import shutil
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed

from yt_dlp import YoutubeDL

from .config import USER_AGENTS, CATEGORY_QUERIES, CATEGORY_NAMES, SKIP_ERRORS, BLACKLIST_KEYWORDS, CLIP_DURATION_SECS, MAX_SEARCH_DURATION_SECS
from .analyzer import VideoAnalyzer
from .utils import clip_video, append_to_url_list, get_video_duration, get_next_index


class YouTubeDownloader:
    """YouTube 다운로더 클래스"""
    
    _file_lock = threading.Lock()

    def __init__(self, output_path, max_duration=180, proxy=None, fast_mode=False, workers=3):
        self.output_path = output_path
        self.max_duration = max_duration # 기본 180초(3분)
        self.proxy = proxy
        self.fast_mode = fast_mode
        self.workers = workers
        self.analyzer = VideoAnalyzer()
        self.query_index = {}

        os.makedirs(output_path, exist_ok=True)

    def _get_ua(self):
        return random.choice(USER_AGENTS)

    def _extract_vid(self, url):
        """URL에서 비디오 ID 추출"""
        if "v=" in url:
            return url.split("v=")[1].split("&")[0]
        return url.split("/")[-1]

    def _load_processed_ids(self):
        """모든 youtube_url_*.txt 파일에서 이미 처리된 비디오 ID 목록 로드"""
        processed_ids = set()
        for filename in os.listdir("."):
            if filename.startswith("youtube_url_") and filename.endswith(".txt"):
                try:
                    with open(filename, 'r', encoding='utf-8') as f:
                        for line in f:
                            if line.strip() and not line.strip().startswith('#'):
                                url = line.split(',')[0].strip()
                                vid = self._extract_vid(url)
                                if vid:
                                    processed_ids.add(vid)
                except:
                    continue
        return processed_ids

    def _get_query(self, category):
        """검색어 순환 반환"""
        if category not in self.query_index:
            self.query_index[category] = 0

        queries = CATEGORY_QUERIES[category]
        query = queries[self.query_index[category]]
        self.query_index[category] = (self.query_index[category] + 1) % len(queries)
        return query

    def _format_duration(self, seconds):
        if not seconds:
            return "?"
        return f"{int(seconds // 60)}:{int(seconds % 60):02d}"

    def _check_1080p_available(self, url):
        """1080p 해상도 사용 가능 여부 확인"""
        try:
            opts = {
                'quiet': True,
                'no_warnings': True,
                'http_headers': {'User-Agent': self._get_ua()},
            }
            if self.proxy:
                opts['proxy'] = self.proxy

            with YoutubeDL(opts) as ydl:
                info = ydl.extract_info(url, download=False)
                if not info:
                    return False, None

                formats = info.get('formats', [])
                for fmt in formats:
                    height = fmt.get('height') or 0
                    if height >= 1080 and fmt.get('vcodec') != 'none':
                        return True, info.get('title', 'Unknown')

                return False, info.get('title', 'Unknown')
        except:
            return False, None

    def _download_one(self, url, quiet=False):
        """단일 영상 다운로드 (HR 1080p + LR 360p)"""
        archive = os.path.join(self.output_path, '.archive.txt')

        # 1080p 사용 가능 여부 먼저 확인
        has_1080p, title = self._check_1080p_available(url)

        hr_file = None
        lr_file = None

        def hook_hr(d):
            nonlocal hr_file
            if d['status'] == 'finished':
                hr_file = d.get('filename')
            elif d['status'] == 'downloading' and not quiet:
                pct = d.get('_percent_str', '0%').strip()
                spd = d.get('_speed_str', 'N/A').strip()
                print(f"\r  HR 다운로드: {pct} | {spd}", end='', flush=True)

        def hook_lr(d):
            nonlocal lr_file
            if d['status'] == 'finished':
                lr_file = d.get('filename')
            elif d['status'] == 'downloading' and not quiet:
                pct = d.get('_percent_str', '0%').strip()
                spd = d.get('_speed_str', 'N/A').strip()
                print(f"\r  LR 다운로드: {pct} | {spd}", end='', flush=True)

        max_retries = 1 if self.fast_mode else 3

        for attempt in range(max_retries):
            try:
                # HR (1080p) 다운로드 - 1080p 있을 때만
                hr_opts = {
                    'outtmpl': os.path.join(self.output_path, '%(title)s_HR.%(ext)s'),
                    'format': 'bestvideo[height>=1080][vcodec^=avc1]/bestvideo[height>=1080][vcodec^=vp9]/bestvideo[height>=1080]',
                    'merge_output_format': 'mp4',
                    'postprocessors': [{
                        'key': 'FFmpegVideoConvertor',
                        'preferedformat': 'mp4',
                    }],
                    'progress_hooks': [hook_hr],
                    'quiet': True,
                    'no_warnings': True,
                    'http_headers': {'User-Agent': self._get_ua()},
                    'socket_timeout': 10 if self.fast_mode else 30,
                }

                # LR (360p) 다운로드 - 항상
                lr_opts = {
                    'outtmpl': os.path.join(self.output_path, '%(title)s_LR.%(ext)s'),
                    'format': 'bestvideo[height<=360][vcodec^=avc1]/bestvideo[height<=360][vcodec^=vp9]/bestvideo[height<=360]/worstvideo',
                    'merge_output_format': 'mp4',
                    'postprocessors': [{
                        'key': 'FFmpegVideoConvertor',
                        'preferedformat': 'mp4',
                    }],
                    'progress_hooks': [hook_lr],
                    'quiet': True,
                    'no_warnings': True,
                    'http_headers': {'User-Agent': self._get_ua()},
                    'socket_timeout': 10 if self.fast_mode else 30,
                }

                if self.proxy:
                    hr_opts['proxy'] = self.proxy
                    lr_opts['proxy'] = self.proxy

                if attempt > 0:
                    time.sleep(min(2 ** attempt, 10))

                # HR 다운로드 (1080p 있을 때만)
                if has_1080p:
                    with YoutubeDL(hr_opts) as ydl:
                        info = ydl.extract_info(url, download=True)
                        if info is None:
                            return "skipped", None, None
                        title = info.get('title', 'Unknown')
                    if not quiet:
                        print()  # 줄바꿈
                else:
                    if not quiet:
                        print(f"  ⚠ 1080p 없음 - LR만 저장")

                # LR 다운로드 (항상)
                with YoutubeDL(lr_opts) as ydl:
                    info = ydl.extract_info(url, download=True)
                    if info is None:
                        return "skipped", None, None
                    if not title:
                        title = info.get('title', 'Unknown')

                if not quiet:
                    print()  # 줄바꿈

                # 아카이브에 기록
                with open(archive, 'a') as f:
                    vid = url.split('=')[-1] if '=' in url else url.split('/')[-1]
                    f.write(f"youtube {vid}\n")

                # has_1080p 정보도 함께 반환
                return "ok", (hr_file, lr_file, has_1080p), title

            except Exception as e:
                err = str(e).lower()

                if "already" in err or "recorded" in err:
                    return "skipped", None, None

                if any(s in err for s in SKIP_ERRORS):
                    return "unavailable", None, None

        return "failed", None, None

    def _search(self, query, count=10):
        """영상 검색"""
        try:
            opts = {
                'quiet': True,
                'no_warnings': True,
                'extract_flat': 'in_playlist',
                'http_headers': {'User-Agent': self._get_ua()},
                'socket_timeout': 10,
            }
            if self.proxy:
                opts['proxy'] = self.proxy

            with YoutubeDL(opts) as ydl:
                result = ydl.extract_info(f"ytsearch{count}:{query}", download=False)

            return list(result.get('entries', [])) if result else []
        except Exception as e:
            print(f"  검색 에러: {e}")
            return []

    def _get_duration(self, video_id):
        """영상 길이 조회"""
        try:
            url = f"https://www.youtube.com/watch?v={video_id}"
            opts = {
                'quiet': True,
                'no_warnings': True,
                'http_headers': {'User-Agent': self._get_ua()},
                'socket_timeout': 5,
            }
            if self.proxy:
                opts['proxy'] = self.proxy

            with YoutubeDL(opts) as ydl:
                info = ydl.extract_info(url, download=False)
                return info.get('duration')
        except:
            return None

    def _process_video(self, entry, category, cat_name):
        """단일 영상 처리 (다운로드 + 분석 + 자동 트리밍 + URL 기록)"""
        vid = entry.get('id')
        url = f"https://www.youtube.com/watch?v={vid}"
        title = entry.get('title', '?')[:45]

        status, filepaths, _ = self._download_one(url, quiet=True)

        result_info = {'title': title, 'status': status, 'saved': False}

        # filepaths는 이제 (hr_file, lr_file, has_1080p) 튜플
        if status == "ok" and filepaths:
            hr_file, lr_file, has_1080p = filepaths

            # 분석할 파일 결정 (HR 우선, 없으면 LR)
            analyze_file = hr_file if (hr_file and os.path.exists(hr_file)) else lr_file

            if analyze_file and os.path.exists(analyze_file):
                print(f"  🔍 분석 중...")
                analysis = self.analyzer.analyze(analyze_file, target_category=category)

                detected = []
                if analysis['face']:
                    detected.append(f"얼굴({analysis['face_count']})")
                if analysis['text']:
                    detected.append("텍스트")
                if analysis['license_plate']:
                    detected.append("번호판")
                if analysis['tattoo']:
                    detected.append("타투")

                result_info['detected'] = detected

                if analysis.get(category):
                    # HR/LR 폴더 생성
                    hr_dir = os.path.join(self.output_path, cat_name, "HR")
                    lr_dir = os.path.join(self.output_path, cat_name, "LR")
                    os.makedirs(hr_dir, exist_ok=True)
                    os.makedirs(lr_dir, exist_ok=True)

                    # 파일명 접두어 결정 (license_plate -> license)
                    prefix = category.replace('license_plate', 'license')
                    url_file_path = f"youtube_url_{category}.txt"
                    duration = get_video_duration(analyze_file)

                    # 모든 카테고리: 3분 이상이면 감지 시점 기준 트리밍
                    detection_secs = analysis.get('detection_secs', [])
                    if not detection_secs and analysis.get('first_detection_sec') is not None:
                        detection_secs = [analysis['first_detection_sec']]

                    # 감지 시점이 없으면 영상 시작점 사용
                    if not detection_secs:
                        detection_secs = [0]

                    selected_secs = []
                    if detection_secs:
                        detection_secs.sort()
                        last_sec = -float('inf')
                        for sec in detection_secs:
                            if sec >= last_sec + CLIP_DURATION_SECS:
                                selected_secs.append(sec)
                                last_sec = sec

                    # 번호판: 최대 3개 클립, 나머지 카테고리: 1개만
                    if category == 'license_plate':
                        selected_secs = selected_secs[:3]
                    else:
                        selected_secs = selected_secs[:1]

                    for i, sec in enumerate(selected_secs):
                        m, s = int(sec // 60), int(sec % 60)
                        ts = f"{m:02d}:{s:02d}"
                        append_to_url_list(url_file_path, url, ts, category)

                        suffix = f"_{i+1}" if len(selected_secs) > 1 else ""

                        # LR 인덱스를 기준으로 사용 (HR/LR 동일 파일명)
                        with self._file_lock:
                            # LR 인덱스 획득 (기준 인덱스)
                            idx = get_next_index(lr_dir, prefix)
                            filename = f"{prefix}_{idx:04d}{suffix}.mp4"
                            hr_dest = os.path.join(hr_dir, filename)
                            lr_dest = os.path.join(lr_dir, filename)

                        # 3분 이상이면 동일한 시간대로 트리밍
                        if duration > CLIP_DURATION_SECS:
                            mins = CLIP_DURATION_SECS // 60
                            print(f"  ✂ 클립 {i+1}: {mins}분 트리밍 ({ts} 기준)")

                            # HR 저장 (1080p 있을 때만)
                            if has_1080p and hr_file and os.path.exists(hr_file):
                                clip_video(hr_file, hr_dest, sec)

                            # LR 저장 (항상)
                            if lr_file and os.path.exists(lr_file):
                                clip_video(lr_file, lr_dest, sec)
                        else:
                            # HR 저장 (1080p 있을 때만)
                            if has_1080p and hr_file and os.path.exists(hr_file):
                                shutil.copy2(hr_file, hr_dest)

                            # LR 저장 (항상)
                            if lr_file and os.path.exists(lr_file):
                                shutil.copy2(lr_file, lr_dest)

                    result_info['clip_count'] = len(selected_secs)
                    result_info['has_1080p'] = has_1080p

                    # 원본 파일 삭제
                    for f in [hr_file, lr_file]:
                        if f and os.path.exists(f):
                            try: os.remove(f)
                            except: pass

                    result_info['saved'] = True
                else:
                    if category == 'license_plate':
                        dest_dir = os.path.join(self.output_path, "번호판_미감지")
                        os.makedirs(dest_dir, exist_ok=True)
                        if hr_file and os.path.exists(hr_file):
                            dest = os.path.join(dest_dir, os.path.basename(hr_file))
                            if not os.path.exists(dest):
                                shutil.move(hr_file, dest)
                        result_info['undetected_saved'] = True
                    # LR 파일 삭제
                    if lr_file and os.path.exists(lr_file):
                        try: os.remove(lr_file)
                        except: pass
                    # HR 파일 삭제 (번호판 아닌 경우)
                    if category != 'license_plate' and hr_file and os.path.exists(hr_file):
                        try: os.remove(hr_file)
                        except: pass

        return result_info

    def collect(self, category, max_videos=5):
        """카테고리별 영상 수집 - 키워드별 균등 분배"""
        cat_name = CATEGORY_NAMES[category]
        queries = CATEGORY_QUERIES[category]
        num_queries = len(queries)

        # 키워드별 할당량 계산 (균등 분배)
        base_count = max_videos // num_queries
        remainder = max_videos % num_queries

        mode = "⚡ 고속" if self.fast_mode else "일반"
        search_limit = MAX_SEARCH_DURATION_SECS

        print(f"\n{'='*60}")
        print(f"[{cat_name}] 키워드 {num_queries}개로 균등 분배")
        print(f"목표: {max_videos}개 | 검색제한: {self._format_duration(search_limit)} | {mode}")
        print('='*60)

        filtered = []
        processed_ids = self._load_processed_ids()

        # 각 키워드별로 검색
        for i, query in enumerate(queries):
            # 이 키워드의 할당량 (나머지는 앞쪽 키워드에 1개씩 추가)
            quota = base_count + (1 if i < remainder else 0)
            if quota == 0:
                continue

            print(f"\n[키워드 {i+1}/{num_queries}] {query} (목표: {quota}개)")

            entries = self._search(query, quota * 3)
            if not entries:
                print("  검색 결과 없음")
                continue

            keyword_filtered = 0
            for entry in entries:
                if not entry: continue

                vid = entry.get('id')
                if not vid: continue

                # 이미 처리된 영상은 즉시 패스
                if vid in processed_ids:
                    print(f"  ⏭ [기록됨] {vid}")
                    continue

                # 이미 이번 수집에서 추가된 영상인지 체크
                if any(e.get('id') == vid for e in filtered):
                    continue

                title = entry.get('title', '')
                dur = entry.get('duration') or self._get_duration(vid)

                # 블랙리스트 키워드 체크
                blacklist = BLACKLIST_KEYWORDS.get(category, [])
                if any(kw in title for kw in blacklist):
                    print(f"  ✗ [제외] {title[:40]}...")
                    continue

                # 너무 긴 영상 제외
                if dur and dur < search_limit:
                    filtered.append(entry)
                    keyword_filtered += 1
                    print(f"  ✓ [{self._format_duration(dur)}] {title}")
                    if keyword_filtered >= quota:
                        break
                elif dur:
                    print(f"  ✗ [{self._format_duration(dur)}] (너무 김)")

                if not self.fast_mode:
                    time.sleep(0.3)

            print(f"  → {keyword_filtered}개 선택됨")

        if not filtered:
            print("\n조건 맞는 영상 없음")
            return 0

        print(f"\n총 {len(filtered)}개 선택됨")

        print(f"\n다운로드 및 분석: {len(filtered)}개" + (" (병렬)" if self.fast_mode else ""))
        success = 0

        if self.fast_mode and self.workers > 1:
            with ThreadPoolExecutor(max_workers=self.workers) as executor:
                futures = {
                    executor.submit(self._process_video, entry, category, cat_name): entry
                    for entry in filtered
                }
                for i, future in enumerate(as_completed(futures)):
                    entry = futures[future]
                    title = entry.get('title', '?')[:45]
                    try:
                        result = future.result()
                        print(f"\n[{i+1}/{len(filtered)}] {title}")
                        if result['status'] == "ok":
                            if result.get('detected'):
                                print(f"  감지: {', '.join(result['detected'])}")
                            if result['saved']:
                                clip_count = result.get('clip_count', 1)
                                has_1080p = result.get('has_1080p', True)
                                res_info = "HR+LR" if has_1080p else "LR만"
                                if clip_count > 1:
                                    print(f"  ✅ 저장: {cat_name} ({clip_count}개 클립, {res_info})")
                                else:
                                    print(f"  ✅ 저장: {cat_name} ({res_info})")
                                success += clip_count
                            elif result.get('undetected_saved'):
                                print("  📁 미감지 보관")
                            else:
                                print("  ❌ 미감지 삭제")
                        elif result['status'] == "skipped":
                            print("  ⏭ 이미 있음")
                        elif result['status'] == "unavailable":
                            print("  ⏭ 사용불가")
                        else:
                            print("  ✗ 실패")
                    except Exception as e:
                        print(f"\n[{i+1}/{len(filtered)}] {title}")
                        print(f"  ✗ 에러: {e}")
        else:
            for i, entry in enumerate(filtered):
                vid = entry.get('id')
                title = entry.get('title', '?')[:45]
                print(f"\n[{i+1}/{len(filtered)}] {title}")

                result = self._process_video(entry, category, cat_name)
                if result['status'] == "ok":
                    if result.get('detected'):
                        print(f"  감지: {', '.join(result['detected'])}")
                    if result['saved']:
                        clip_count = result.get('clip_count', 1)
                        has_1080p = result.get('has_1080p', True)
                        res_info = "HR+LR" if has_1080p else "LR만"
                        if clip_count > 1:
                            print(f"  ✅ 저장: {cat_name} ({clip_count}개 클립, {res_info})")
                        else:
                            print(f"  ✅ 저장: {cat_name} ({res_info})")
                        success += clip_count
                    elif result.get('undetected_saved'):
                        print("  📁 미감지 보관")
                    else:
                        print("  ❌ 미감지 삭제")
                elif result['status'] == "skipped":
                    print("  ⏭ 이미 있음")
                elif result['status'] == "unavailable":
                    print("  ⏭ 사용불가")
                elif result['status'] == "no_1080p":
                    print("  ⏭ 1080p 없음")
                else:
                    print("  ✗ 실패")

                if not self.fast_mode:
                    time.sleep(random.uniform(0.5, 1.5))

        return success
