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
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 _download_one(self, url, quiet=False):
        """단일 영상 다운로드"""
        archive = os.path.join(self.output_path, '.archive.txt')
        last_file = None

        def hook(d):
            nonlocal last_file
            if d['status'] == 'finished':
                last_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  다운로드: {pct} | {spd}", end='', flush=True)

        max_retries = 1 if self.fast_mode else 3

        for attempt in range(max_retries):
            try:
                opts = {
                    'outtmpl': os.path.join(self.output_path, '%(title)s.%(ext)s'),
                    'format': 'best[ext=mp4]/best',
                    'progress_hooks': [hook],
                    'quiet': True,
                    'no_warnings': True,
                    'download_archive': archive,
                    'http_headers': {'User-Agent': self._get_ua()},
                    'socket_timeout': 10 if self.fast_mode else 30,
                }

                if self.proxy:
                    opts['proxy'] = self.proxy

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

                with YoutubeDL(opts) as ydl:
                    info = ydl.extract_info(url, download=True)
                    if info is None:
                        return "skipped", None, None

                    title = info.get('title', 'Unknown')

                    if last_file and os.path.exists(last_file):
                        return "ok", last_file, title

                    ext = info.get('ext', 'mp4')
                    path = os.path.join(self.output_path, f"{title}.{ext}")
                    if os.path.exists(path):
                        return "ok", path, title

                    return "ok", None, 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, filepath, _ = self._download_one(url, quiet=True)

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

        if status == "ok" and filepath:
            print(f"  🔍 분석 중...")
            analysis = self.analyzer.analyze(filepath, 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):
                # 1. 태스크별 전용 youtube_url_{category}.txt 업데이트
                url_file_path = f"youtube_url_{category}.txt"
                ts = analysis.get('first_detection_ts', '00:00')
                append_to_url_list(url_file_path, url, ts, category)
                
                # 2. 결과 폴더 이동 및 파일명 변경 (category_0001.mp4 형식)
                dest_dir = os.path.join(self.output_path, cat_name)
                os.makedirs(dest_dir, exist_ok=True)
                
                # 파일명 접두어 결정 (license_plate -> license)
                prefix = category.replace('license_plate', 'license')
                
                with self._file_lock:
                    idx = get_next_index(dest_dir, prefix)
                    new_filename = f"{prefix}_{idx:04d}.mp4"
                    dest = os.path.join(dest_dir, new_filename)
                
                # 원본 길이가 3분(180초) 초과면 감지 시점 기준 트리밍
                duration = get_video_duration(filepath)
                if duration > 180:
                    print(f"  ✂ 3분 초과 영상 자동 트리밍 ({self._format_duration(duration)} -> 3:00)")
                    clip_video(filepath, dest, analysis.get('first_detection_sec', 0))
                else:
                    if not os.path.exists(dest):
                        shutil.move(filepath, dest)
                
                result_info['saved'] = True
                result_info['new_path'] = dest
            else:
                if category == 'license_plate':
                    dest_dir = os.path.join(self.output_path, "번호판_미감지")
                    os.makedirs(dest_dir, exist_ok=True)
                    dest = os.path.join(dest_dir, os.path.basename(filepath))
                    if not os.path.exists(dest):
                        shutil.move(filepath, dest)
                    result_info['undetected_saved'] = True
                else:
                    try:
                        os.remove(filepath)
                    except:
                        pass

        return result_info

    def collect(self, category, max_videos=5):
        """카테고리별 영상 수집"""
        cat_name = CATEGORY_NAMES[category]
        query = self._get_query(category)

        print(f"\n{'='*60}")
        print(f"[{cat_name}] 검색: {query}")
        mode = "⚡ 고속" if self.fast_mode else "일반"
        # 검색 시에는 제한을 20분(1200초)으로 완화하여 더 많은 영상 확보
        search_limit = 1200 
        print(f"목표: {max_videos}개 | 검색제한: {self._format_duration(search_limit)} | {mode}")
        print('='*60)

        # 검색
        entries = self._search(query, max_videos * 3)
        if not entries:
            print("검색 결과 없음")
            return 0

        print(f"검색됨: {len(entries)}개")

        # 필터링
        filtered = []
        processed_ids = self._load_processed_ids()
        
        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

            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

            # 너무 긴 영상(예: 20분 초과) 제외
            if dur and dur < search_limit:
                filtered.append(entry)
                print(f"  ✓ [{self._format_duration(dur)}] {title}")
                if len(filtered) >= max_videos:
                    break
            elif dur:
                print(f"  ✗ [{self._format_duration(dur)}] (너무 filter됨)")

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

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

        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']:
                                new_name = os.path.basename(result['new_path'])
                                print(f"  ✅ 저장: {cat_name}/{new_name}")
                                success += 1
                            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']:
                        new_name = os.path.basename(result['new_path'])
                        print(f"  ✅ 저장: {cat_name}/{new_name}")
                        success += 1
                    elif result.get('undetected_saved'):
                        print("  📁 미감지 보관")
                    else:
                        print("  ❌ 미감지 삭제")
                elif result['status'] == "skipped":
                    print("  ⏭ 이미 있음")
                elif result['status'] == "unavailable":
                    print("  ⏭ 사용불가")
                else:
                    print("  ✗ 실패")

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

        return success
