"""
视频合成器模块
整合视频拼接、花字叠加、旁白配音的完整流程
"""
import os
import time
import logging
from pathlib import Path
from typing import Dict, Any, List, Optional, Union

import config
from modules import ffmpeg_utils, fancy_text, factory, storage
from modules.text_renderer import renderer
from modules import path_utils

logger = logging.getLogger(__name__)


class VideoComposer:
    """视频合成器"""
    
    def __init__(
        self,
        output_dir: str = None,
        target_size: tuple = (1080, 1920),
        voice_type: str = "sweet_female"
    ):
        """
        初始化合成器
        
        Args:
            output_dir: 输出目录
            target_size: 目标分辨率 (width, height)
            voice_type: 默认旁白音色
        """
        self.output_dir = Path(output_dir) if output_dir else config.OUTPUT_DIR
        self.output_dir.mkdir(exist_ok=True)
        self.target_size = target_size
        self.voice_type = voice_type
        
        # 临时文件追踪
        self._temp_files = []
    
    def _add_temp(self, path: str):
        """记录临时文件"""
        if path:
            self._temp_files.append(path)
    
    def cleanup(self):
        """清理临时文件"""
        for f in self._temp_files:
            try:
                if os.path.exists(f):
                    os.remove(f)
            except Exception as e:
                logger.warning(f"Failed to cleanup {f}: {e}")
        self._temp_files = []
    
    def compose(
        self,
        video_paths: List[str],
        subtitles: List[Dict[str, Any]] = None,
        fancy_texts: List[Dict[str, Any]] = None,
        voiceover_text: str = None,
        voiceover_segments: List[Dict[str, Any]] = None,
        bgm_path: str = None,
        bgm_volume: float = 0.15,
        output_name: str = None,
        project_id: Optional[str] = None,
        upload_to_r2: bool = False
    ) -> str:
        """
        完整视频合成流程
        
        Args:
            video_paths: 分镜视频路径列表
            subtitles: 字幕配置列表 [{text, start, duration, style}]
            fancy_texts: 花字配置列表 [{text, style, x, y, start, duration}]
            voiceover_text: 完整旁白文本（会自动生成并混音）
            voiceover_segments: 分段旁白配置 [{text, start}]，与 voiceover_text 二选一
            bgm_path: 背景音乐路径
            bgm_volume: BGM音量
            output_name: 输出文件名（不含扩展名）
            upload_to_r2: 是否上传到R2存储
        
        Returns:
            最终视频路径（或R2 URL）
        """
        if not video_paths:
            raise ValueError("No video paths provided")
        
        timestamp = int(time.time())
        output_name = output_name or f"composed_{timestamp}"
        # Per-project temp dir to avoid cross-project overwrites
        temp_root = path_utils.project_compose_dir(project_id, output_name) if project_id else config.TEMP_DIR
        
        logger.info(f"Starting composition: {len(video_paths)} videos")
        
        try:
            # Step 1: 拼接视频
            merged_path = str(Path(temp_root) / f"{output_name}_merged.mp4")
            ffmpeg_utils.concat_videos(video_paths, merged_path, self.target_size)
            self._add_temp(merged_path)
            current_video = merged_path

            # Step 1.1: 若无音轨，补一条静音底，避免后续滤镜找不到 0:a
            silent_path = str(Path(temp_root) / f"{output_name}_silent.mp4")
            ffmpeg_utils.add_silence_audio(current_video, silent_path)
            self._add_temp(silent_path)
            current_video = silent_path
            
            # Step 2: 添加字幕 (白字黑边，无底框，水平居中)
            if subtitles:
                subtitled_path = str(Path(temp_root) / f"{output_name}_subtitled.mp4")
                subtitle_style = {
                    "font": ffmpeg_utils._get_font_path(),
                    "fontsize": 60,
                    "fontcolor": "white",
                    "borderw": 5,
                    "bordercolor": "black",
                    "box": 0,  # 无底框
                    "x": "(w-text_w)/2",  # 水平居中
                    "y": "h-200",  # 底部区域
                }
                ffmpeg_utils.add_multiple_subtitles(
                    current_video, subtitles, subtitled_path, default_style=subtitle_style
                )
                self._add_temp(subtitled_path)
                current_video = subtitled_path
            
            # Step 3: 叠加花字 (支持原子化参数)
            if fancy_texts:
                overlay_configs = []
                for ft in fancy_texts:
                    text = ft.get("text", "")
                    style = ft.get("style")
                    custom_style = ft.get("custom_style")
                    
                    # 如果 style 是字典，说明是原子化参数，直接使用
                    if isinstance(style, dict):
                        img_path = renderer.render(text, style, cache=False)
                    elif custom_style and isinstance(custom_style, dict):
                        # 兼容旧逻辑：如果有 custom_style，尝试通过原子化渲染器渲染
                        if "font_size" in custom_style:
                             img_path = renderer.render(text, custom_style, cache=False)
                        else:
                             # 回退到旧版 fancy_text
                             img_path = fancy_text.create_fancy_text(
                                text=text,
                                style=style if isinstance(style, str) else "subtitle",
                                custom_style={
                                    **(custom_style or {}),
                                    "font_name": config.DEFAULT_FONT,
                                },
                                cache=False
                            )
                    else:
                        # 旧版逻辑
                        img_path = fancy_text.create_fancy_text(
                            text=text,
                            style=style if isinstance(style, str) else "subtitle",
                            custom_style={
                                "font_name": config.DEFAULT_FONT,
                            },
                            cache=False
                        )
                        
                    overlay_configs.append({
                        "path": img_path,
                        "x": ft.get("x", "(W-w)/2"),
                        "y": ft.get("y", "(H-h)/2"),
                        "start": ft.get("start", 0),
                        "duration": ft.get("duration", 999)
                    })
                
                fancy_path = str(Path(temp_root) / f"{output_name}_fancy.mp4")
                ffmpeg_utils.overlay_multiple_images(
                    current_video, overlay_configs, fancy_path
                )
                self._add_temp(fancy_path)
                current_video = fancy_path
            
            # Step 4: 生成并混合旁白（火山 WS 优先，失败回退 Edge）
            if voiceover_text:
                vo_out = str(Path(temp_root) / f"{output_name}_vo_full.mp3")
                vo_path = factory.generate_voiceover_volcengine(
                    text=voiceover_text,
                    voice_type=self.voice_type,
                    output_path=vo_out,
                )
                self._add_temp(vo_path)
                
                voiced_path = str(Path(temp_root) / f"{output_name}_voiced.mp4")
                ffmpeg_utils.mix_audio(
                    current_video, vo_path, voiced_path,
                    audio_volume=1.5,
                    video_volume=0.2
                )
                self._add_temp(voiced_path)
                current_video = voiced_path
            
            elif voiceover_segments:
                current_video = self._add_segmented_voiceover(
                    current_video, voiceover_segments, output_name, Path(temp_root)
                )
            
            # Step 5: 添加BGM（淡入淡出，若 duck 失败会自动退回低音量混合）
            if bgm_path:
                bgm_output = str(Path(temp_root) / f"{output_name}_bgm.mp4")
                ffmpeg_utils.add_bgm(
                    current_video, bgm_path, bgm_output,
                    bgm_volume=bgm_volume,
                    ducking=False,  # 为避免兼容性问题，这里禁用 duck，保持低音量
                    duck_gain_db=-6.0,
                    fade_in=1.0,
                    fade_out=1.0
                )
                self._add_temp(bgm_output)
                current_video = bgm_output
            
            # Step 6: 输出最终文件
            final_path = str(self.output_dir / f"{output_name}.mp4")
            
            # 复制到输出目录
            import shutil
            shutil.copy(current_video, final_path)
            
            logger.info(f"Composition complete: {final_path}")
            
            # 上传到R2
            if upload_to_r2:
                r2_url = storage.upload_file(final_path)
                logger.info(f"Uploaded to R2: {r2_url}")
                return r2_url
            
            return final_path
            
        finally:
            # 清理临时文件（保留最终输出）
            self.cleanup()
    
    def _add_segmented_voiceover(
        self,
        video_path: str,
        segments: List[Dict[str, Any]],
        output_name: str,
        temp_root: Path,
    ) -> str:
        """添加分段旁白"""
        if not segments:
            return video_path
        
        # 为每段生成音频
        audio_files = []
        for i, seg in enumerate(segments):
            text = seg.get("text", "")
            if not text:
                continue
            
            voice = seg.get("voice_type", self.voice_type)
            audio_path = factory.generate_voiceover_volcengine(
                text=text,
                voice_type=voice,
                output_path=str(temp_root / f"{output_name}_seg_{i}.mp3")
            )
            
            if audio_path:
                audio_files.append({
                    "path": audio_path,
                    "start": seg.get("start", 0)
                })
                self._add_temp(audio_path)
        
        if not audio_files:
            return video_path
        
        # 依次混入音频
        current = video_path
        for i, af in enumerate(audio_files):
            output = str(temp_root / f"{output_name}_seg_mixed_{i}.mp4")
            ffmpeg_utils.mix_audio(
                current, af["path"], output,
                audio_volume=1.0,
                video_volume=0.2 if i == 0 else 1.0,  # 只在第一次降低原视频音量
                audio_start=af["start"]
            )
            self._add_temp(output)
            current = output
        
        return current

    def compose_from_script(
        self,
        script: Dict[str, Any],
        video_map: Dict[int, str],
        bgm_path: str = None,
        output_name: str = None,
        project_id: Optional[str] = None,
    ) -> str:
        """
        基于生成脚本和视频映射进行合成
        
        Args:
            script: 标准化分镜脚本
            video_map: 场景ID到视频路径的映射
            bgm_path: BGM路径
            output_name: 输出文件名
        """
        scenes = script.get("scenes", [])
        if not scenes:
            raise ValueError("Empty script")
            
        video_paths = []
        fancy_texts = []
        
        # 1. 收集视频路径和花字 (按分镜顺序)
        total_duration = 0.0
        
        for scene in scenes:
            scene_id = scene["id"]
            video_path = video_map.get(scene_id)
            
            if not video_path or not os.path.exists(video_path):
                logger.warning(f"Missing video for scene {scene_id}, skipping")
                continue
            
            # 获取实际视频时长
            try:
                info = ffmpeg_utils.get_video_info(video_path)
                duration = float(info.get("duration", 5.0))
            except:
                duration = 5.0
            
            video_paths.append(video_path)
            
            # 花字 (白字黑边，无底框，固定在上半区域居中)
            if "fancy_text" in scene:
                ft = scene["fancy_text"]
                if isinstance(ft, dict):
                    text = ft.get("text", "")
                    
                    if text:
                        # 固定样式：白字黑边，无底框
                        fixed_style = {
                            "font_size": 72,
                            "font_color": "#FFFFFF",
                            "stroke": {"color": "#000000", "width": 5}
                            # 无 background，不加底框
                        }

                        # 让花字时长默认跟随镜头（不改 prompt，仅纠正过短/缺失 duration）
                        start_in_scene = float(ft.get("start_time", 0) or 0.0)
                        if start_in_scene < 0:
                            start_in_scene = 0.0
                        if start_in_scene >= duration:
                            start_in_scene = 0.0
                        ft_dur = ft.get("duration", None)
                        try:
                            ft_dur_val = float(ft_dur) if ft_dur is not None else None
                        except Exception:
                            ft_dur_val = None
                        # If too short, extend to scene end
                        if ft_dur_val is None or ft_dur_val < 1.5:
                            ft_dur_val = max(duration - start_in_scene, 1.5)
                        # Clamp within scene duration
                        ft_dur_val = max(0.5, min(ft_dur_val, duration))

                        fancy_texts.append({
                            "text": text,
                            "style": fixed_style,
                            "x": "(W-w)/2",  # 居中
                            "y": "180",      # 上半区域
                            "start": total_duration + start_in_scene,
                            "duration": ft_dur_val
                        })
            
            total_duration += duration

        # 2. 拼接视频
        timestamp = int(time.time())
        output_name = output_name or f"composed_{timestamp}"
        temp_root = path_utils.project_compose_dir(project_id, output_name) if project_id else config.TEMP_DIR
        
        merged_path = str(Path(temp_root) / f"{output_name}_merged.mp4")
        ffmpeg_utils.concat_videos(video_paths, merged_path, self.target_size)
        self._add_temp(merged_path)
        current_video = merged_path
        
        # 3. 处理整体旁白时间轴 (New Logic)
        voiceover_timeline = script.get("voiceover_timeline", [])
        mixed_audio_path = str(Path(temp_root) / f"{output_name}_mixed_vo.mp3")
        
        # 初始化静音底轨 (长度为 total_duration)
        ffmpeg_utils._run_ffmpeg([
            ffmpeg_utils.FFMPEG_PATH, "-y",
            "-f", "lavfi", "-i", "anullsrc=r=44100:cl=stereo",
            "-t", str(total_duration),
            "-c:a", "mp3",
            mixed_audio_path
        ])
        self._add_temp(mixed_audio_path)
        
        subtitles = []
        
        if voiceover_timeline:
            for i, item in enumerate(voiceover_timeline):
                text = item.get("text", "")
                sub_text = item.get("subtitle", text)
                
                # 支持两种格式：
                # 新格式: start_time (秒), duration (秒) - 直接使用绝对时间
                # 旧格式: start_ratio (0-1), duration_ratio (0-1) - 按比例计算
                if "start_time" in item:
                    # 新格式：直接使用秒
                    target_start = float(item.get("start_time", 0))
                    target_duration = float(item.get("duration", 3))
                else:
                    # 旧格式：按比例计算（向后兼容）
                    start_ratio = float(item.get("start_ratio", 0))
                    duration_ratio = float(item.get("duration_ratio", 0))
                    target_start = start_ratio * total_duration
                    target_duration = duration_ratio * total_duration
                
                if not text: continue
                
                # 生成 TTS
                tts_path = factory.generate_voiceover_volcengine(
                    text=text,
                    voice_type=self.voice_type,
                    output_path=str(Path(temp_root) / f"{output_name}_vo_{i}.mp3")
                )
                self._add_temp(tts_path)
                
                # 调整时长
                adjusted_path = str(Path(temp_root) / f"{output_name}_vo_adj_{i}.mp3")
                ffmpeg_utils.adjust_audio_duration(tts_path, target_duration, adjusted_path)
                self._add_temp(adjusted_path)
                
                # 混合到总音轨
                new_mixed = str(Path(temp_root) / f"{output_name}_mixed_{i}.mp3")
                ffmpeg_utils.mix_audio_at_offset(mixed_audio_path, adjusted_path, target_start, new_mixed)
                mixed_audio_path = new_mixed # Update current mixed path
                self._add_temp(new_mixed)
                
                # 添加字幕配置 (完全同步)
                subtitles.append({
                    "text": ffmpeg_utils.wrap_text_smart(sub_text),
                    "start": target_start,
                    "duration": target_duration,
                    "style": {} # Default
                })
        
        # 4. 将合成好的旁白混入视频
        voiced_path = str(Path(temp_root) / f"{output_name}_voiced.mp4")
        ffmpeg_utils.mix_audio(
            current_video, mixed_audio_path, voiced_path,
            audio_volume=1.5,
            video_volume=0.2 # 压低原音
        )
        self._add_temp(voiced_path)
        current_video = voiced_path
        
        # 5. 添加字幕 (使用新的 ffmpeg_utils.add_multiple_subtitles)
        if subtitles:
            subtitled_path = str(Path(temp_root) / f"{output_name}_subtitled.mp4")
            subtitle_style = {
                "font": ffmpeg_utils._get_font_path(),
                "fontsize": 60,
                "fontcolor": "white",
                "borderw": 5,
                "bordercolor": "black",
                "box": 0,  # 无底框
                "x": "(w-text_w)/2",  # 水平居中
                "y": "h-200",  # 底部区域
            }
            ffmpeg_utils.add_multiple_subtitles(
                current_video, subtitles, subtitled_path, default_style=subtitle_style
            )
            self._add_temp(subtitled_path)
            current_video = subtitled_path
            
        # 6. 添加花字
        if fancy_texts:
            fancy_path = str(Path(temp_root) / f"{output_name}_fancy.mp4")
            
            overlay_configs = []
            for ft in fancy_texts:
                # 渲染花字图片
                img_path = renderer.render(ft["text"], ft["style"], cache=False)
                overlay_configs.append({
                    "path": img_path,
                    "x": ft["x"],
                    "y": ft["y"],
                    "start": ft["start"],
                    "duration": ft["duration"]
                })
                
            ffmpeg_utils.overlay_multiple_images(
                current_video, overlay_configs, fancy_path
            )
            self._add_temp(fancy_path)
            current_video = fancy_path
            
        # 7. 添加 BGM
        if bgm_path:
            bgm_output = str(Path(temp_root) / f"{output_name}_bgm.mp4")
            ffmpeg_utils.add_bgm(
                current_video, bgm_path, bgm_output,
                bgm_volume=0.15
            )
            self._add_temp(bgm_output)
            current_video = bgm_output
            
        # 8. 输出最终文件
        final_path = str(self.output_dir / f"{output_name}.mp4")
        import shutil
        shutil.copy(current_video, final_path)
        
        logger.info(f"Composition complete: {final_path}")
        
        self.cleanup()
        return final_path


    def compose_standard_task(self, task_config: Dict[str, Any]) -> str:
        """
        执行标准合成任务 (Legacy)
        """
        settings = task_config.get("settings", {})
        self.voice_type = settings.get("voice_type", self.voice_type)
        
        # 1. 准备视频片段
        video_paths = []
        for seg in task_config.get("segments", []):
            path = seg.get("path") or seg.get("video_path")
            if not path: continue
            video_paths.append(path)
            
        # 2. 解析时间轴
        subtitles = []
        fancy_texts = []
        voiceover_segments = []
        
        for item in task_config.get("timeline", []):
            itype = item.get("type")

            if not itype:
                if "text" in item and ("style" in item or "x" in item or "y" in item):
                    itype = "fancy_text"
                elif "text" in item and "duration" in item and "start" in item:
                    itype = "subtitle"
                elif "text" in item and "start" in item:
                    itype = "voiceover"
                else:
                    continue

            if itype == "subtitle":
                subtitles.append(item)
            elif itype == "fancy_text":
                if "x" not in item and "position" in item:
                    item["x"] = item["position"].get("x")
                    item["y"] = item["position"].get("y")
                fancy_texts.append(item)
            elif itype == "voiceover":
                voiceover_segments.append(item)
        
        return self.compose(
            video_paths=video_paths,
            subtitles=subtitles,
            fancy_texts=fancy_texts,
            voiceover_segments=voiceover_segments,
            bgm_path=settings.get("bgm_path"),
            bgm_volume=settings.get("bgm_volume", 0.06),
            output_name=settings.get("output_name"),
            upload_to_r2=settings.get("upload_to_r2", False)
        )


def compose_product_video(
    video_paths: List[str],
    subtitle_configs: List[Dict[str, Any]] = None,
    fancy_text_configs: List[Dict[str, Any]] = None,
    voiceover_text: str = None,
    bgm_path: str = None,
    output_path: str = None,
    voice_type: str = "sweet_female"
) -> str:
    """便捷函数：合成商品短视频"""
    composer = VideoComposer(voice_type=voice_type)
    
    output_name = None
    if output_path:
        output_name = Path(output_path).stem
        composer.output_dir = Path(output_path).parent
    
    return composer.compose(
        video_paths=video_paths,
        subtitles=subtitle_configs,
        fancy_texts=fancy_text_configs,
        voiceover_text=voiceover_text,
        bgm_path=bgm_path,
        output_name=output_name
    )


def quick_compose(
    video_folder: str,
    script: List[Dict[str, Any]],
    output_path: str = None,
    voice_type: str = "sweet_female",
    bgm_path: str = None
) -> str:
    """快速合成：从文件夹读取视频，配合脚本合成"""
    folder = Path(video_folder)
    
    video_files = sorted([
        f for f in folder.iterdir() 
        if f.suffix.lower() in ['.mp4', '.mov', '.avi', '.mkv']
    ])
    
    video_paths = []
    subtitles = []
    fancy_texts = []
    voiceovers = []
    
    current_time = 0
    
    for i, item in enumerate(script):
        if "video" in item:
            vp = folder / item["video"]
        elif i < len(video_files):
            vp = video_files[i]
        else:
            logger.warning(f"No video for script item {i}")
            continue
        
        video_paths.append(str(vp))
        
        try:
            info = ffmpeg_utils.get_video_info(str(vp))
            duration = info.get("duration", 5)
        except:
            duration = item.get("duration", 5)
        
        if "subtitle" in item:
            subtitles.append({
                "text": item["subtitle"],
                "start": current_time,
                "duration": duration,
                "style": item.get("subtitle_style", {})
            })
        
        if "fancy_text" in item:
            ft = item["fancy_text"]
            if isinstance(ft, str):
                ft = {"text": ft}
            fancy_texts.append({
                "text": ft.get("text", ""),
                "style": ft.get("style", "highlight"),
                "custom_style": ft.get("custom_style"),
                "x": ft.get("x", "(W-w)/2"),
                "y": ft.get("y", 200),
                "start": current_time,
                "duration": duration
            })
        
        if "voiceover" in item:
            voiceovers.append(item["voiceover"])
        
        current_time += duration
    
    voiceover_text = "。".join(voiceovers) if voiceovers else None
    
    return compose_product_video(
        video_paths=video_paths,
        subtitle_configs=subtitles if subtitles else None,
        fancy_text_configs=fancy_texts if fancy_texts else None,
        voiceover_text=voiceover_text,
        bgm_path=bgm_path,
        output_path=output_path,
        voice_type=voice_type
    )


# ============================================================
# 示例用法
# ============================================================

def example_hairclip_video():
    """示例：发夹商品视频合成"""
    素材目录 = config.BASE_DIR / "素材" / "发夹" / "合成图拆分镜"
    
    video_paths = [
        str(素材目录 / "视频-分镜1.mp4"),
        str(素材目录 / "视频-分镜2.mp4"),
        str(素材目录 / "视频-分镜3.mp4"),
        str(素材目录 / "视频-分镜4.mp4"),
        str(素材目录 / "视频-分镜5.mp4"),
    ]
    
    script = [
        {
            "subtitle": "塌马尾 vs 高颅顶",
            "fancy_text": {
                "text": "塌马尾 vs 高颅顶",
                "style": "comparison",
                "y": 150
            },
            "voiceover": "普通马尾和高颅顶马尾的区别，你看出来了吗",
        },
        {
            "subtitle": "3秒出门，无需皮筋",
            "fancy_text": {"text": "发量+50%", "style": "bubble", "y": 300},
            "voiceover": "只需要三秒钟，不需要皮筋，发量瞬间增加百分之五十",
        },
        {
            "subtitle": "发量+50%",
            "voiceover": "蓬松的高颅顶效果，让你瞬间变美",
        },
        {
            "subtitle": "狂甩不掉！",
            "fancy_text": {"text": "狂甩不掉！", "style": "warning", "y": 400},
            "voiceover": "而且超级牢固，怎么甩都不会掉",
        },
        {
            "subtitle": "¥3.99 立即抢购",
            "fancy_text": {"text": "3.99", "style": "price", "y": 500},
            "voiceover": "只要三块九毛九，点击下方链接立即购买",
        },
    ]
    
    output = quick_compose(
        video_folder=str(素材目录),
        script=script,
        output_path=str(config.OUTPUT_DIR / "发夹_合成视频.mp4"),
        voice_type="sweet_female"
    )
    
    print(f"视频合成完成: {output}")
    return output


if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    example_hairclip_video()
