"""
B站视频下载模块
"""
import os
import subprocess
from pathlib import Path
from typing import Dict, Optional
from datetime import datetime
import requests
from tqdm import tqdm


class BilibiliDownloader:
    """B站视频下载器"""
    
    HEADERS = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
        'Referer': 'https://www.bilibili.com'
    }
    
    def __init__(self, output_dir: str = "downloads", cookies: Optional[Dict] = None):
        """初始化下载器
        
        Args:
            output_dir: 输出目录
            cookies: 登录Cookie
        """
        self.output_dir = Path(output_dir)
        self.output_dir.mkdir(exist_ok=True)
        self.cookies = cookies or {}
        self.session = requests.Session()
        self.session.headers.update(self.HEADERS)
        if self.cookies:
            self.session.cookies.update(self.cookies)
    
    def download_file(self, url: str, output_path: Path, desc: str = "下载中") -> bool:
        """下载单个文件(支持断点续传)
        
        Args:
            url: 下载链接
            output_path: 输出路径
            desc: 进度条描述
        
        Returns:
            是否下载成功
        """
        try:
            # 检查是否已存在部分下载
            temp_path = output_path.with_suffix(output_path.suffix + '.tmp')
            downloaded_size = temp_path.stat().st_size if temp_path.exists() else 0
            
            headers = self.HEADERS.copy()
            if downloaded_size > 0:
                headers['Range'] = f'bytes={downloaded_size}-'
            
            response = self.session.get(url, headers=headers, stream=True, timeout=30)
            response.raise_for_status()
            
            total_size = int(response.headers.get('content-length', 0))
            if downloaded_size > 0:
                total_size += downloaded_size
            
            mode = 'ab' if downloaded_size > 0 else 'wb'
            
            with open(temp_path, mode) as f:
                with tqdm(
                    total=total_size,
                    initial=downloaded_size,
                    unit='B',
                    unit_scale=True,
                    unit_divisor=1024,
                    desc=desc
                ) as pbar:
                    for chunk in response.iter_content(chunk_size=8192):
                        if chunk:
                            f.write(chunk)
                            pbar.update(len(chunk))
            
            # 下载完成,重命名
            temp_path.rename(output_path)
            return True
            
        except Exception as e:
            print(f"\n❌ 下载失败: {e}")
            return False
    
    def merge_video_audio(self, video_path: Path, audio_path: Path, output_path: Path) -> bool:
        """合并视频和音频文件(使用ffmpeg)
        
        Args:
            video_path: 视频文件路径
            audio_path: 音频文件路径
            output_path: 输出文件路径
        
        Returns:
            是否合并成功
        """
        try:
            # 检查ffmpeg是否可用
            result = subprocess.run(
                ['ffmpeg', '-version'],
                capture_output=True,
                timeout=5
            )
            if result.returncode != 0:
                raise Exception("ffmpeg不可用,请先安装ffmpeg")
            
            print(f"\n正在合并音视频...")
            
            cmd = [
                'ffmpeg',
                '-i', str(video_path),
                '-i', str(audio_path),
                '-c', 'copy',
                '-y',  # 覆盖已存在文件
                str(output_path)
            ]
            
            result = subprocess.run(
                cmd,
                capture_output=True,
                timeout=300
            )
            
            if result.returncode == 0:
                print(f"✅ 合并完成: {output_path.name}")
                # 删除临时文件
                video_path.unlink()
                audio_path.unlink()
                return True
            else:
                print(f"❌ 合并失败: {result.stderr.decode()}")
                return False
                
        except subprocess.TimeoutExpired:
            print("❌ 合并超时")
            return False
        except FileNotFoundError:
            print("❌ 未找到ffmpeg,请先安装: brew install ffmpeg (macOS) 或 apt install ffmpeg (Linux)")
            return False
        except Exception as e:
            print(f"❌ 合并失败: {e}")
            return False
    
    def sanitize_filename(self, filename: str) -> str:
        """清理文件名中的非法字符"""
        illegal_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|']
        for char in illegal_chars:
            filename = filename.replace(char, '_')
        return filename.strip()
    
    def get_video_dir(self, bvid: str, title: str = None) -> Path:
        """获取视频专属目录(按日期和BV号+标题分类)
        
        格式: downloads/YYYY-MM-DD/BV号_标题/
        例如: downloads/2026-02-27/BV1ms4y1Y76i_细胞聚团原因剖析/
        
        Args:
            bvid: BV号
            title: 视频标题(可选)
        
        Returns:
            视频目录路径
        """
        # 获取当前日期
        date_str = datetime.now().strftime("%Y-%m-%d")
        
        # 创建目录名: BV号_标题
        if title:
            safe_title = self.sanitize_filename(title)
            # 限制标题长度,避免路径过长
            if len(safe_title) > 50:
                safe_title = safe_title[:50]
            dir_name = f"{bvid}_{safe_title}"
        else:
            dir_name = bvid
        
        # 创建目录结构: downloads/日期/BV号_标题/
        video_dir = self.output_dir / date_str / dir_name
        video_dir.mkdir(parents=True, exist_ok=True)
        
        return video_dir
    
    def download_video(
        self,
        video_url: str,
        audio_url: Optional[str],
        title: str,
        bvid: str,
        quality_name: str,
        merge_needed: bool = True
    ) -> bool:
        """下载视频(包含音视频合并)
        
        Args:
            video_url: 视频流URL
            audio_url: 音频流URL(可选)
            title: 视频标题
            bvid: BV号
            quality_name: 清晰度名称
            merge_needed: 是否需要合并音视频
        
        Returns:
            是否下载成功
        """
        # 获取视频专属目录(包含标题)
        video_dir = self.get_video_dir(bvid, title)
        
        # 生成文件名(只用标题)
        safe_title = self.sanitize_filename(title)
        filename = f"{safe_title}.mp4"
        output_path = video_dir / filename
        
        # 如果文件已存在
        if output_path.exists():
            print(f"⚠️  文件已存在: {output_path.relative_to(self.output_dir)}")
            return True
        
        print(f"\n开始下载: {title}")
        print(f"清晰度: {quality_name}")
        print(f"BV号: {bvid}")
        print(f"保存位置: {video_dir.relative_to(self.output_dir)}/")
        print("-" * 50)
        
        if not merge_needed:
            # 单文件下载(MP4格式)
            success = self.download_file(video_url, output_path, "下载视频")
            if success:
                print(f"\n✅ 下载完成: {output_path.relative_to(self.output_dir)}")
            return success
        
        # DASH格式需要分别下载并合并
        video_temp = video_dir / f"{bvid}_video.m4s"
        audio_temp = video_dir / f"{bvid}_audio.m4s"
        
        try:
            # 下载视频流
            print("\n[1/3] 下载视频流...")
            if not self.download_file(video_url, video_temp, "视频"):
                return False
            
            # 下载音频流
            if audio_url:
                print("\n[2/3] 下载音频流...")
                if not self.download_file(audio_url, audio_temp, "音频"):
                    return False
            
            # 合并音视频
            print("\n[3/3] 合并音视频...")
            success = self.merge_video_audio(video_temp, audio_temp, output_path)
            
            if success:
                print(f"\n🎉 全部完成!")
                print(f"保存位置: {output_path.relative_to(self.output_dir)}")
            
            return success
            
        except Exception as e:
            print(f"\n❌ 下载过程出错: {e}")
            # 清理临时文件
            if video_temp.exists():
                video_temp.unlink()
            if audio_temp.exists():
                audio_temp.unlink()
            return False
    
    def download_cover(self, cover_url: str, bvid: str, title: str = None) -> Optional[Path]:
        """下载视频封面
        
        Args:
            cover_url: 封面URL
            bvid: BV号
            title: 视频标题(可选)
        
        Returns:
            封面文件路径
        """
        try:
            # 保存到视频专属目录
            video_dir = self.get_video_dir(bvid, title)
            output_path = video_dir / f"{bvid}_cover.jpg"
            
            if output_path.exists():
                return output_path
            
            response = requests.get(cover_url, timeout=10)
            response.raise_for_status()
            
            output_path.write_bytes(response.content)
            print(f"✅ 封面已保存: {output_path.relative_to(self.output_dir)}")
            return output_path
            
        except Exception as e:
            print(f"⚠️  下载封面失败: {e}")
            return None
