import subprocess
import os
import uuid
import logging
import asyncio
from typing import Dict, Any, List, Optional
from src.config.settings import UPLOAD_DIR, DATA_DIR
from src.services.project_service import project_manager
from src.models.schemas import Storyboard

logger = logging.getLogger(__name__)

class VideoExportService:
    def __init__(self):
        self.output_dir = os.path.join(UPLOAD_DIR, "exports")
        os.makedirs(self.output_dir, exist_ok=True)

    async def export_project(self, project_id: str, format: str = "mp4") -> Dict[str, Any]:
        """ 导出 project to video file.
        Uses ffmpeg to stitch storyboards (images/videos) and audio.
        """
        try:
            logger.info(f"Starting export for project {project_id}")
            project = project_manager.get_project(project_id)
            if not project:
                raise ValueError(f"Project {project_id} not found")

            if not project.storyboards:
                raise ValueError("No storyboards to export")

            # 过滤 storyboards that have visual content
            valid_storyboards = [
                s for s in project.storyboards 
                if (s.video_urls and s.video_urls[0]) or (s.image_urls and s.image_urls[0])
            ]
            
            if not valid_storyboards:
                raise ValueError("No valid visual content found in storyboards")

            # Resolve paths
            base_dir = os.getcwd()  # suming running from root
            
            segments = []
            
            for sb in valid_storyboards:
                # Determine visual source
                if sb.video_urls and sb.video_urls[0]:
                    source = sb.video_urls[0]
                    is_video = True
                else:
                    source = sb.image_urls[0]
                    is_video = False
                
                # 转换 relative URL to absolute path
                if source.startswith("/uploads"):
                     source_path = os.path.join(base_dir, source.lstrip("/"))
                elif source.startswith("/files"):
                     rel_path = source[7:]
                     source_path = os.path.join(DATA_DIR, rel_path)
                else:
                    source_path = source 
                
                # Determine audio source
                audio_path = None
                if sb.audio_url:
                    if sb.audio_url.startswith("/uploads"):
                        audio_path = os.path.join(base_dir, sb.audio_url.lstrip("/"))
                    elif sb.audio_url.startswith("/files"):
                        rel_path = sb.audio_url[7:]
                        audio_path = os.path.join(DATA_DIR, rel_path)
                    else:
                        audio_path = sb.audio_url
                
                # 持续时间
                duration = 5.0
                if sb.duration:
                    try:
                        # 解析 duration string (e.g. "5s" -> 5.0)
                        d_str = sb.duration.lower().replace('s', '')
                        duration = float(d_str)
                    except (ValueError, AttributeError):
                        pass
                
                segments.append({
                    "visual": source_path,
                    "audio": audio_path,
                    "is_video": is_video,
                    "duration": duration
                })

            temp_files = []
            
            for i, seg in enumerate(segments):
                temp_output = os.path.join(self.output_dir, f"{project_id}_seg_{i}.ts")
                
                # Construct ffmpeg command for this segment
                # We use .ts (MPEG transport stream) for easier concatenation
                
                cmd = ["ffmpeg", "-y"]
                
                # Inputs
                if seg["is_video"]:
                    cmd.extend(["-i", seg["visual"]])
                else:
                    cmd.extend(["-loop", "1", "-i", seg["visual"]])
                
                if seg["audio"]:
                    cmd.extend(["-i", seg["audio"]])
                else:
                    # 生成 silent audio
                    cmd.extend(["-f", "lavfi", "-i", "anullsrc=channel_layout=stereo:sample_rate=44100"])

                # 过滤 complex
                # Scale to 1280x720, pad if necessary to keep aspect ratio
                video_filter = "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2,setsar=1"
                
                if not seg["is_video"]:
                    # For images, we limit duration
                     cmd.extend(["-t", str(seg["duration"])])
                
                cmd.extend([
                    "-vf", video_filter,
                    "-c:v", "libx264", "-pix_fmt", "yuv420p",
                    "-c:a", "aac", 
                    "-bsf:v", "h264_mp4toannexb", # 导入ant for TS concat
                    "-f", "mpegts",
                    # -shortest is dangerous if audio is shorter than video or vice versa.
                    # We want video to determine length OR image duration.
                    # image + audio: max(duration, audio_len)? 
                    # Usually -shortest cuts to shortest stream.
                    # For images, we explicitly set -t for image, that sets video stream length.
                    # audio is longer, it gets cut. If shorter, silence padding?
                    # MVP, just let it be.
                    temp_output
                ])
                
                # Run sync
                logger.info(f"Processing segment {i}: {cmd}")
                proc = await asyncio.create_subprocess_exec(
                    *cmd, 
                    stdout=asyncio.subprocess.PIPE,
                    stderr=asyncio.subprocess.PIPE
                )
                stdout, stderr = await proc.communicate()
                
                if proc.returncode != 0:
                     logger.error(f"FFmpeg failed for segment {i}: {stderr.decode()}")
                     raise RuntimeError(f"Failed to process segment {i}")
                
                temp_files.append(temp_output)

            # Concat
            concat_str = "|".join(temp_files)
            final_output_name = f"{project_id}_{uuid.uuid4().hex[:8]}.mp4"
            final_output_path = os.path.join(self.output_dir, final_output_name)
            
            # concat protocol
            concat_cmd = [
                "ffmpeg", "-y",
                "-i", f"concat:{concat_str}",
                "-c", "copy",
                "-bsf:a", "aac_adtstoasc",
                final_output_path
            ]
            
            logger.info(f"Concatenating: {concat_cmd}")
            proc = await asyncio.create_subprocess_exec(
                 *concat_cmd,
                 stdout=asyncio.subprocess.PIPE,
                 stderr=asyncio.subprocess.PIPE
            )
            stdout, stderr = await proc.communicate()
            
            if proc.returncode != 0:
                logger.error(f"FFmpeg concat failed: {stderr.decode()}")
                raise RuntimeError("Failed to stitch video")
            
            # 清理up temp files
            for tf in temp_files:
                try:
                    os.remove(tf)
                except OSError:
                    pass
            
            return {
                "status": "SUCCEEDED",
                "video_url": f"/uploads/exports/{final_output_name}"
            }

        except Exception as e:
            logger.error(f"Export failed: {str(e)}")
            raise e
