import asyncio
import logging
import uuid
import json
from typing import Optional, Dict, Any, List

from src.models.schemas import ScriptResponse, Storyboard
from src.services.agent_engine import AgentScopeService
from src.services.provider.registry import ModelRegistry, ModelType
from src.services.user_api_key_service import user_api_key_service
import src.config.settings as settings
from .pipeline import ScriptAnalysisPipeline
from .utils import split_chapters
import os

logger = logging.getLogger(__name__)

class ScriptService:
    def __init__(self):
        # Ensure AgentScope is initialized via the main service
        try:
            AgentScopeService().init_agentscope()
        except Exception as e:
            logger.warning(f"Failed to trigger AgentScopeService init: {e}")

        # 延迟初始化 pipeline，避免启动时需要 API Key
        self._pipeline = None

    @property
    def pipeline(self):
        """延迟初始化 pipeline"""
        if self._pipeline is None:
            self._pipeline = ScriptAnalysisPipeline()
        return self._pipeline

    def _get_user_api_key(self, user_id: str, provider: str) -> Optional[str]:
        """获取用户的 API Key（纯用户密钥模式）"""
        if not user_id:
            return None
        try:
            return user_api_key_service.get_user_provider_key(user_id, provider)
        except Exception as e:
            logger.warning(f"Failed to get user API key for provider {provider}: {e}")
            return None

    def _resolve_provider_from_model(self, model_name: str) -> Optional[str]:
        """从模型名称解析供应商"""
        if "/" in model_name:
            return model_name.split("/", 1)[0]
        # 尝试从 registry 查找
        svc_config = ModelRegistry.get_config(model_name)
        if svc_config:
            return svc_config.get("provider")
        return None

    async def run_full_initialization(self,
                                      novel_text: str,
                                      project_id: str,
                                      model_name: Optional[str] = None,
                                      provider: Optional[str] = None,
                                      base_url: Optional[str] = None,
                                      style_id: Optional[str] = None,
                                      progress_callback: Optional[callable] = None,
                                      user_id: Optional[str] = None,
                                      **kwargs) -> Dict[str, Any]:
        """
        Run the complete initialization pipeline in background:
        Style -> Split -> Summary -> Extract -> Storyboard

        Args:
            model_name: Name of the model to use (e.g. "qwen-plus", "gpt-4o")
            provider: Optional provider name (e.g. "dashscope", "openai")
            base_url: Custom API base URL for OpenAI compatible models
            progress_callback: Optional callback function to report progress
                               Signature: callback(step, percentage, message, details)
            user_id: User ID for retrieving user's API key (纯用户密钥模式)
        """
        # 纯用户密钥模式：从用户配置获取 API Key
        resolved_provider = provider or (self._resolve_provider_from_model(model_name) if model_name else None)
        api_key = None
        if user_id and resolved_provider:
            api_key = self._get_user_api_key(user_id, resolved_provider)
            if not api_key:
                raise RuntimeError(f"No API key configured for provider '{resolved_provider}'. "
                                   f"Please add your API key in Settings > API Keys.")

        if model_name or base_url or provider or api_key:
            # 创建新的 pipeline 实例，覆盖延迟初始化的 property
            self._pipeline = ScriptAnalysisPipeline(model_name=model_name, base_url=base_url, api_key=api_key, provider=provider)

        def report_progress(step: str, percentage: int, message: str, details: dict = None):
            if progress_callback:
                try:
                    progress_callback(step, percentage, message, details or {})
                except Exception as e:
                    logger.error(f"Progress callback failed: {e}")

        # 1. Split Chapters (Regex default for efficiency)
        report_progress("splitting_chapters", 15, "正在拆分章节...", {})
        chapters = await self.split_chapters(novel_text, use_agent=False, model_name=model_name, language=kwargs.get("language", "Chinese"))
        report_progress("splitting_chapters", 20, f"章节拆分完成，共 {len(chapters)} 章", {"chapters_count": len(chapters)})
        
        # 3. Summarize Global & Chapters
        report_progress("summarizing", 25, "正在生成全局摘要...", {})
        summary_context = novel_text[:50000]
        summary_res = await self.pipeline.run_summarization(summary_context, **kwargs)
        
        # Handle case where summarization returns plain string instead of dict
        if isinstance(summary_res, dict):
            global_summary = summary_res.get("summary", "")
            project_title = summary_res.get("title", "Untitled Project")
        else:
            global_summary = str(summary_res)
            project_title = "Untitled Project"
            
        # Chapter Summaries (Parallel with error handling)
        report_progress("summarizing_chapters", 30, "正在生成章节摘要...", {"chapters_count": len(chapters)})
        
        async def summarize_chapter(chapter):
            try:
                content = chapter.get("content", "")
                if not content: return ""
                res = await self.pipeline.run_summarization(content[:20000], global_summary=global_summary, **kwargs)
                if isinstance(res, dict):
                    return res.get("summary", "")
                return str(res)
            except Exception as e:
                logger.error(f"Failed to summarize chapter {chapter.get('title', 'Unknown')}: {e}")
                return ""  # 返回 empty summary on failure

        chapter_tasks = [summarize_chapter(ch) for ch in chapters]
        chapter_summaries = await asyncio.gather(*chapter_tasks, return_exceptions=False)
        
        for i, ch in enumerate(chapters):
            ch["summary"] = chapter_summaries[i]
            if "id" not in ch: ch["id"] = str(uuid.uuid4())
            ch["order"] = i + 1

        # 4. Extract Assets (Fanout)
        report_progress("extracting_assets", 40, "正在提取角色、场景和道具...", {})
        extraction_res = await self._run_extraction_pipeline(novel_text, chapters, global_summary, **kwargs)
        
        final_chars = extraction_res["characters"]
        final_scenes = extraction_res["scenes"]
        final_props = extraction_res["props"]
        
        report_progress("extracting_assets", 55, "资产提取完成", {
            "characters_count": len(final_chars),
            "scenes_count": len(final_scenes),
            "props_count": len(final_props)
        })

        # 5. Storyboard (Generate for ALL chapters)
        report_progress("generating_storyboards", 60, "正在生成分镜...", {})
        all_storyboards = {}
        if chapters:
            known_assets = {"characters": final_chars, "scenes": final_scenes, "props": final_props}
            semaphore = asyncio.Semaphore(10)  # 并发限制为10

            async def process_chapter_storyboard(chapter):
                async with semaphore:
                    try:
                        sb_res = await self.pipeline.run_storyboard_splitting(
                            chapter["content"], 
                            kwargs.get("language", "Chinese"), 
                            known_assets
                        )
                        return chapter["id"], sb_res.get("storyboards", [])
                    except Exception as e:
                        logger.error(f"Failed to generate storyboard for chapter {chapter.get('title')}: {e}")
                        return chapter["id"], []

            storyboard_tasks = [process_chapter_storyboard(ch) for ch in chapters]
            results = await asyncio.gather(*storyboard_tasks)
            
            for ch_id, sbs in results:
                all_storyboards[ch_id] = sbs
            
            # Attach storyboards to chapters for the response if needed, 
            # or just return them structured by chapter ID
            for ch in chapters:
                ch["storyboards"] = all_storyboards.get(ch["id"], [])

        return {
            "project_id": project_id,
            "title": project_title,
            "description": global_summary,
            "style_id": style_id,
            "chapters": chapters,
            "assets": {
                "characters": final_chars,
                "scenes": final_scenes,
                "props": final_props
            },
            "preview_storyboards": []
        }

    async def run_script_initialization(self,
                                        novel_text: str,
                                        project_id: str,
                                        model_name: Optional[str] = None,
                                        provider: Optional[str] = None,
                                        base_url: Optional[str] = None,
                                        progress_callback: Optional[callable] = None,
                                        user_id: Optional[str] = None,
                                        **kwargs) -> Dict[str, Any]:
        """
        Run the screenplay-first initialization pipeline:
        Split -> Summary -> Multi-agent adaptation discussion -> Screenplay drafting
        """
        resolved_provider = provider or (self._resolve_provider_from_model(model_name) if model_name else None)
        api_key = None
        if user_id and resolved_provider:
            api_key = self._get_user_api_key(user_id, resolved_provider)
            if not api_key:
                raise RuntimeError(f"No API key configured for provider '{resolved_provider}'. "
                                   f"Please add your API key in Settings > API Keys.")

        if model_name or base_url or provider or api_key:
            self._pipeline = ScriptAnalysisPipeline(model_name=model_name, base_url=base_url, api_key=api_key, provider=provider)

        def report_progress(step: str, percentage: int, message: str, details: dict = None):
            if progress_callback:
                try:
                    progress_callback(step, percentage, message, details or {})
                except Exception as e:
                    logger.error(f"Progress callback failed: {e}")

        report_progress("splitting_chapters", 15, "正在拆分小说章节...", {})
        chapters = await self.split_chapters(novel_text, use_agent=False, model_name=model_name, language=kwargs.get("language", "Chinese"))
        report_progress("splitting_chapters", 25, f"章节拆分完成，共 {len(chapters)} 章", {"chapters_count": len(chapters)})

        report_progress("summarizing", 35, "正在生成改编摘要...", {})
        summary_context = novel_text[:50000]
        summary_res = await self.pipeline.run_summarization(summary_context, **kwargs)
        if isinstance(summary_res, dict):
            global_summary = summary_res.get("summary", "")
            project_title = summary_res.get("title", "Untitled Script Project")
        else:
            global_summary = str(summary_res)
            project_title = "Untitled Script Project"

        report_progress("discussing_adaptation", 50, "多 Agent 正在讨论改编策略...", {})
        discussion = await self.pipeline.run_script_discussion(summary_context, language=kwargs.get("language", "Chinese"))
        adaptation_strategy = discussion.get("adaptation_strategy", global_summary)
        dialogue_guidelines = discussion.get("dialogue_guidelines", [])

        report_progress("writing_screenplay", 65, "正在生成文字剧本初稿...", {"chapters_count": len(chapters)})

        async def adapt_chapter(index: int, chapter: Dict[str, Any]) -> Dict[str, Any]:
            content = chapter.get("content", "")
            if not content:
                return {
                    "id": chapter.get("id") or str(uuid.uuid4()),
                    "title": chapter.get("title") or f"第 {index + 1} 集",
                    "order": index + 1,
                    "content": "",
                    "summary": "",
                    "source_content": "",
                }
            draft = await self.pipeline.run_screenplay_adaptation(
                source_text=content,
                global_summary=global_summary,
                adaptation_strategy=adaptation_strategy,
                dialogue_guidelines=dialogue_guidelines,
                language=kwargs.get("language", "Chinese"),
            )
            return {
                "id": chapter.get("id") or str(uuid.uuid4()),
                "title": draft.get("title") or chapter.get("title") or f"第 {index + 1} 集",
                "order": index + 1,
                "content": draft.get("screenplay", ""),
                "summary": draft.get("summary", ""),
                "source_content": content,
            }

        screenplay_chapters = await asyncio.gather(
            *(adapt_chapter(index, chapter) for index, chapter in enumerate(chapters)),
            return_exceptions=False
        )

        report_progress("finalizing", 90, "正在整理剧本项目...", {"episodes_count": len(screenplay_chapters)})

        return {
            "project_id": project_id,
            "title": project_title,
            "description": adaptation_strategy,
            "chapters": screenplay_chapters,
            "discussion": discussion,
        }

    async def _run_extraction_pipeline(self, novel_text, chapters, summary, **kwargs):

        # Reusing the logic from analyze_novel for map-reduce extraction
        # ... (Simplified version of lines 52-99 in original analyze_novel) ...
        # First, let's just run extraction on the first 30k chars or map-reduce if needed
        
        if len(novel_text) < 30000:
             res = await self.pipeline.run_extraction_fanout(novel_text, summary, **kwargs)
             return res
        else:
             # 映射-Reduce
             tasks = []
             batch_size = 5
             for i in range(0, len(chapters), batch_size):
                 batch = chapters[i:i+batch_size]
                 for ch in batch:
                     tasks.append(self.pipeline.run_extraction_fanout(ch["content"], summary, **kwargs))
             
             results = await asyncio.gather(*tasks)
             # Deduplicate
             all_c, all_s, all_p = [], [], []
             for r in results:
                 all_c.extend(r.get("characters", []))
                 all_s.extend(r.get("scenes", []))
                 all_p.extend(r.get("props", []))
             
             # 并行 deduplication with error handling
             dedup_chars_task = self.pipeline.run_deduplication(all_c, "character", **kwargs)
             dedup_scenes_task = self.pipeline.run_deduplication(all_s, "scene", **kwargs)
             dedup_props_task = self.pipeline.run_deduplication(all_p, "prop", **kwargs)
             
             try:
                 d_chars, d_scenes, d_props = await asyncio.gather(
                     dedup_chars_task, 
                     dedup_scenes_task, 
                     dedup_props_task,
                     return_exceptions=False
                 )
             except Exception as e:
                 logger.error(f"Deduplication failed: {e}, using raw assets")
                 # Fallback to non-deduplicated assets
                 d_chars = {"assets": all_c}
                 d_scenes = {"assets": all_s}
                 d_props = {"assets": all_p}
             return {
                 "characters": d_chars.get("assets", []),
                 "scenes": d_scenes.get("assets", []),
                 "props": d_props.get("assets", [])
             }

    async def analyze_novel(self,
                            novel_text: str,
                            project_id: str,
                            model_name: Optional[str] = None,
                            instruction: Optional[str] = None,
                            skip_storyboard: bool = False,
                            user_id: Optional[str] = None,
                            **kwargs) -> ScriptResponse:
        """
        Comprehensive analysis of a novel using AgentScope agents.
        Supports automatic chunking and map-reduce for long texts to avoid token limits.

        Args:
            user_id: User ID for retrieving user's API key (纯用户密钥模式)
        """
        # 纯用户密钥模式：获取用户 API Key
        resolved_provider = self._resolve_provider_from_model(model_name) if model_name else None
        api_key = None
        if user_id and resolved_provider:
            api_key = self._get_user_api_key(user_id, resolved_provider)
            if not api_key:
                raise RuntimeError(f"No API key configured for provider '{resolved_provider}'. "
                                   f"Please add your API key in Settings > API Keys.")

        # 更新 pipeline model if needed
        if model_name or api_key:
            self._pipeline = ScriptAnalysisPipeline(model_name=model_name, api_key=api_key)

        # 1. Split Chapters
        chapters = await self.split_chapters(novel_text)
        
        # 2. Summarize (Agent)
        summary_context = novel_text[:50000] if len(novel_text) > 50000 else novel_text
        summary_res = await self.pipeline.run_summarization(summary_context, **kwargs)
        summary_text = summary_res.get("summary", "")
        title = summary_res.get("title", "Untitled")

        # 3. Extraction Strategy (Map-Reduce vs Single Pass)
        final_chars = []
        final_scenes = []
        final_props = []

        # Threshold: > 20k chars AND multiple chapters triggers Map-Reduce
        if len(novel_text) > 20000 and len(chapters) > 1:
            logger.info(f"Novel too long ({len(novel_text)} chars), using Map-Reduce extraction on {len(chapters)} chapters.")
            
            all_characters = []
            all_scenes = []
            all_props = []
            
            # 映射 Step: Process chapters in batches to control concurrency
            batch_size = 5
            for i in range(0, len(chapters), batch_size):
                batch_chapters = chapters[i:i+batch_size]
                tasks = []
                for chapter in batch_chapters:
                    chapter_text = chapter.get("content", "")
                    if not chapter_text: continue
                    tasks.append(self.pipeline.run_extraction_fanout(chapter_text, summary_text, **kwargs))
                
                results = await asyncio.gather(*tasks)
                
                for res in results:
                    if isinstance(res, Exception):
                        logger.error(f"Extraction task failed: {res}")
                        continue
                    all_characters.extend(res.get("characters", []))
                    all_scenes.extend(res.get("scenes", []))
                    all_props.extend(res.get("props", []))
            
            # Reduce Step: Deduplicate
            # Inject 'type' for deduplicator
            for c in all_characters: c['type'] = 'character'
            for s in all_scenes: s['type'] = 'scene'
            for p in all_props: p['type'] = 'prop'
            
            # 并行 deduplication with error handling
            dedup_chars_task = self.pipeline.run_deduplication(all_characters, "character", **kwargs)
            dedup_scenes_task = self.pipeline.run_deduplication(all_scenes, "scene", **kwargs)
            dedup_props_task = self.pipeline.run_deduplication(all_props, "prop", **kwargs)
            
            try:
                d_chars, d_scenes, d_props = await asyncio.gather(
                    dedup_chars_task, 
                    dedup_scenes_task, 
                    dedup_props_task,
                    return_exceptions=False
                )
            except Exception as e:
                logger.error(f"Deduplication failed: {e}, using raw assets")
                # Fallback to non-deduplicated assets
                d_chars = {"assets": all_characters}
                d_scenes = {"assets": all_scenes}
                d_props = {"assets": all_props}
            
            final_chars = d_chars.get("assets", [])
            final_scenes = d_scenes.get("assets", [])
            final_props = d_props.get("assets", [])
            
        else:
            # 传统的 Single Pass (for short stories/chapters)
            # Use fanout extraction directly
            res = await self.pipeline.run_extraction_fanout(novel_text, summary_text, **kwargs)
            final_chars = res.get("characters", [])
            final_scenes = res.get("scenes", [])
            final_props = res.get("props", [])

        # Ensure IDs for assets (critical for linking)
        for char in final_chars:
            if "id" not in char: char["id"] = str(uuid.uuid4())
        for scene in final_scenes:
            if "id" not in scene: scene["id"] = str(uuid.uuid4())
        for prop in final_props:
            if "id" not in prop: prop["id"] = str(uuid.uuid4())

        # 创建 lookups for mapping names to IDs
        char_map = {c["name"]: c["id"] for c in final_chars}
        # scene_map = {s["name"]: s["id"] for s in final_scenes} # Reserved for future use
        # prop_map = {p["name"]: p["id"] for p in final_props} # Reserved for future use

        # 4. Storyboard Extraction
        final_storyboards = []
        
        # Ensure chapters have IDs
        for i, chapter in enumerate(chapters):
            if "id" not in chapter:
                chapter["id"] = str(uuid.uuid4())
            chapter["order"] = i + 1

        if not skip_storyboard:
            # Run storyboard splitting for each chapter
            storyboard_tasks = []
            valid_chapters = []
            
            for chapter in chapters:
                chapter_text = chapter.get("content", "")
                if not chapter_text: continue
                
                valid_chapters.append(chapter)
                
                # 传递已知资产以帮助一致性
                known_assets = {
                    "characters": final_chars,
                    "scenes": final_scenes,
                    "props": final_props
                }
                
                storyboard_tasks.append(
                    self.pipeline.run_storyboard_splitting(
                        novel_text=chapter_text, 
                        language=kwargs.get("language", "Chinese"),
                        known_assets=known_assets
                    )
                )

            if storyboard_tasks:
                try:
                    storyboard_results = await asyncio.gather(*storyboard_tasks, return_exceptions=False)
                except Exception as e:
                    logger.error(f"Storyboard generation failed: {e}")
                    storyboard_results = []

                # 进程 results
                for idx, res in enumerate(storyboard_results):
                    chapter = valid_chapters[idx]
                    chapter_id = chapter["id"]
                    shots = res.get("storyboards", [])
                    
                    for shot in shots:
                        # 映射 character names to IDs
                        char_ids = []
                        for name in shot.get("character_list", []):
                            # 简单 fuzzy match or exact match
                            matched_id = None
                            for c_name, c_id in char_map.items():
                                if c_name in name or name in c_name:
                                    matched_id = c_id
                                    break
                            if matched_id:
                                char_ids.append(matched_id)

                        # 创建 Storyboard object with fallback defaults for cinematic fields
                        from src.config import settings
                        import json
                        cinematic_defaults = {
                            "camera_angle": "平视",
                            "lens": "标准镜头",
                            "focus": "自动对焦",
                            "lighting": "电影感光效",
                            "color_style": "电影感"
                        }
                        try:
                            config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "config", "generation_options.json")
                            with open(config_path, 'r', encoding='utf-8') as f:
                                options = json.load(f)
                                if 'cinematic' in options:
                                    cinematic = options['cinematic']
                                    cinematic_defaults = {
                                        "camera_angle": cinematic.get('cameraAngles', {}).get('default', '平视'),
                                        "lens": cinematic.get('lenses', {}).get('default', '标准镜头'),
                                        "focus": cinematic.get('focus', {}).get('default', '自动对焦'),
                                        "lighting": cinematic.get('lighting', {}).get('default', '电影感光效'),
                                        "color_style": cinematic.get('colorStyle', {}).get('default', '电影感')
                                    }
                        except Exception as e:
                            logger.warning(f"Failed to load cinematic defaults: {e}")
                        
                        sb = Storyboard(
                            id=str(uuid.uuid4()),
                            episodeId=chapter_id,
                            order=shot.get("shot_number", 0),
                            shot=shot.get("shot_title", ""),
                            desc=shot.get("visual_description", ""),
                            duration=shot.get("duration", "3s"),
                            type=shot.get("shot_type", "Medium Shot"),
                            cameraMovement=shot.get("camera_movement"),
                            cameraAngle=shot.get("camera_angle") or cinematic_defaults["camera_angle"],
                            lens=shot.get("lens") or cinematic_defaults["lens"],
                            focus=shot.get("focus") or cinematic_defaults["focus"],
                            lighting=shot.get("lighting") or cinematic_defaults["lighting"],
                            colorStyle=shot.get("color_style") or cinematic_defaults["color_style"],
                            transition=shot.get("transition"),
                            audioDesc=shot.get("audio_description"),
                            characterIds=list(set(char_ids)), # Dedupe
                            sceneId=None, # Scene mapping is complex, leaving explicit for now
                            propIds=[],
                            location=shot.get("location"),
                            time=shot.get("time_of_day"),
                            originalText=shot.get("original_text"),
                            mergeImagePrompt=shot.get("merge_image_prompt"),
                            videoPrompt=shot.get("video_prompt")
                        )
                        final_storyboards.append(sb)

        # 5. Aggregate
        return ScriptResponse(
            project_id=project_id,
            title=title,
            summary=summary_text,
            characters=final_chars,
            scenes=final_scenes,
            props=final_props,
            chapters=chapters,
            script_scenes=final_storyboards,
            character_list=[c.get('name') for c in final_chars]
        )

    async def optimize_prompt(self,
                              prompt: str,
                              target_type: str = "image",
                              template: str = "general",
                              model_name: Optional[str] = None,
                              provider: Optional[str] = None,
                              language: str = "Chinese",
                              user_id: Optional[str] = None,
                              **kwargs) -> str:
        # 纯用户密钥模式：获取用户 API Key
        resolved_provider = provider or (self._resolve_provider_from_model(model_name) if model_name else None)
        api_key = None
        if user_id and resolved_provider:
            api_key = self._get_user_api_key(user_id, resolved_provider)
            if not api_key:
                raise RuntimeError(f"No API key configured for provider '{resolved_provider}'. "
                                   f"Please add your API key in Settings > API Keys.")

        if model_name or api_key:
            self._pipeline = ScriptAnalysisPipeline(model_name=model_name, provider=provider, api_key=api_key)
        return await self.pipeline.run_prompt_optimization(prompt, target_type, template, language)

    async def recommend_style(self,
                              novel_text: str,
                              available_styles: List[str],
                              model_name: Optional[str] = None,
                              provider: Optional[str] = None,
                              user_id: Optional[str] = None,
                              **kwargs) -> Dict[str, Any]:
        """Recommend an art style based on novel text.

        Args:
            novel_text: The novel text to analyze
            available_styles: List of available style descriptions
            model_name: Model to use for recommendation
            provider: Provider name
            user_id: User ID for retrieving user's API key (纯用户密钥模式)
        """
        # 纯用户密钥模式：获取用户 API Key
        resolved_provider = provider or (self._resolve_provider_from_model(model_name) if model_name else None)
        api_key = None
        if user_id and resolved_provider:
            api_key = self._get_user_api_key(user_id, resolved_provider)
            if not api_key:
                raise RuntimeError(f"No API key configured for provider '{resolved_provider}'. "
                                   f"Please add your API key in Settings > API Keys.")

        if model_name or api_key:
            self._pipeline = ScriptAnalysisPipeline(model_name=model_name, provider=provider, api_key=api_key)

        # Use pipeline to get style recommendation
        # This will use the model_config to call the LLM
        return await self.pipeline.run_style_recommendation(novel_text, available_styles)

    async def summarize_novel(self,
                              novel_text: str,
                              language: str = "Chinese",
                              model_name: Optional[str] = None,
                              provider: Optional[str] = None,
                              global_summary: Optional[str] = None,
                              user_id: Optional[str] = None,
                              **kwargs) -> Dict[str, Any]:
        # 纯用户密钥模式：获取用户 API Key
        resolved_provider = provider or (self._resolve_provider_from_model(model_name) if model_name else None)
        api_key = None
        if user_id and resolved_provider:
            api_key = self._get_user_api_key(user_id, resolved_provider)
            if not api_key:
                raise RuntimeError(f"No API key configured for provider '{resolved_provider}'. "
                                   f"Please add your API key in Settings > API Keys.")

        if model_name or api_key:
            self._pipeline = ScriptAnalysisPipeline(model_name=model_name, provider=provider, api_key=api_key)
        return await self.pipeline.run_summarization(novel_text, language, global_summary)

    async def extract_characters(self,
                                 novel_text: str,
                                 language: str = "Chinese",
                                 global_summary: Optional[str] = None,
                                 known_characters: Optional[List[Dict[str, Any]]] = None,
                                 model_name: Optional[str] = None,
                                 provider: Optional[str] = None,
                                 user_id: Optional[str] = None,
                                 **kwargs) -> Dict[str, Any]:
        # 纯用户密钥模式：获取用户 API Key
        resolved_provider = provider or (self._resolve_provider_from_model(model_name) if model_name else None)
        api_key = None
        if user_id and resolved_provider:
            api_key = self._get_user_api_key(user_id, resolved_provider)
            if not api_key:
                raise RuntimeError(f"No API key configured for provider '{resolved_provider}'. "
                                   f"Please add your API key in Settings > API Keys.")

        if model_name or api_key:
            self._pipeline = ScriptAnalysisPipeline(model_name=model_name, provider=provider, api_key=api_key)
        res = await self.pipeline.run_extraction_fanout(novel_text, global_summary or "", language, {"characters": known_characters})
        return {"characters": res["characters"]}

    async def extract_scenes(self,
                             novel_text: str,
                             language: str = "Chinese",
                             global_summary: Optional[str] = None,
                             known_scenes: Optional[List[Dict[str, Any]]] = None,
                             model_name: Optional[str] = None,
                             user_id: Optional[str] = None,
                             **kwargs) -> Dict[str, Any]:
        # 纯用户密钥模式：获取用户 API Key
        resolved_provider = self._resolve_provider_from_model(model_name) if model_name else None
        api_key = None
        if user_id and resolved_provider:
            api_key = self._get_user_api_key(user_id, resolved_provider)
            if not api_key:
                raise RuntimeError(f"No API key configured for provider '{resolved_provider}'. "
                                   f"Please add your API key in Settings > API Keys.")

        if model_name or api_key:
            self._pipeline = ScriptAnalysisPipeline(model_name=model_name, api_key=api_key)
        res = await self.pipeline.run_extraction_fanout(novel_text, global_summary or "", language, {"scenes": known_scenes})
        return {"scenes": res["scenes"]}

    async def extract_props(self,
                            novel_text: str,
                            language: str = "Chinese",
                            global_summary: Optional[str] = None,
                            known_props: Optional[List[Dict[str, Any]]] = None,
                            model_name: Optional[str] = None,
                            provider: Optional[str] = None,
                            user_id: Optional[str] = None,
                            **kwargs) -> Dict[str, Any]:
        # 纯用户密钥模式：获取用户 API Key
        resolved_provider = provider or (self._resolve_provider_from_model(model_name) if model_name else None)
        api_key = None
        if user_id and resolved_provider:
            api_key = self._get_user_api_key(user_id, resolved_provider)
            if not api_key:
                raise RuntimeError(f"No API key configured for provider '{resolved_provider}'. "
                                   f"Please add your API key in Settings > API Keys.")

        if model_name or api_key:
            self._pipeline = ScriptAnalysisPipeline(model_name=model_name, provider=provider, api_key=api_key)
        res = await self.pipeline.run_extraction_fanout(novel_text, global_summary or "", language, {"props": known_props})
        return {"props": res["props"]}

    async def split_storyboards(self,
                                novel_text: str,
                                project_id: Optional[str] = None,
                                model_name: Optional[str] = None,
                                language: str = "Chinese",
                                known_characters: Optional[List[Dict[str, Any]]] = None,
                                known_scenes: Optional[List[Dict[str, Any]]] = None,
                                known_props: Optional[List[Dict[str, Any]]] = None,
                                user_id: Optional[str] = None,
                                **kwargs) -> Dict[str, Any]:
        # 纯用户密钥模式：获取用户 API Key
        resolved_provider = self._resolve_provider_from_model(model_name) if model_name else None
        api_key = None
        if user_id and resolved_provider:
            api_key = self._get_user_api_key(user_id, resolved_provider)
            if not api_key:
                raise RuntimeError(f"No API key configured for provider '{resolved_provider}'. "
                                   f"Please add your API key in Settings > API Keys.")

        if model_name or api_key:
            self._pipeline = ScriptAnalysisPipeline(model_name=model_name, api_key=api_key)
        known_assets = {
            "characters": known_characters,
            "scenes": known_scenes,
            "props": known_props
        }
        return await self.pipeline.run_storyboard_splitting(novel_text, language, known_assets)

    async def deduplicate_assets(self,
                                 assets: List[Dict[str, Any]],
                                 language: str = "Chinese",
                                 model_name: Optional[str] = None,
                                 user_id: Optional[str] = None,
                                 **kwargs) -> Dict[str, Any]:
        # 纯用户密钥模式：获取用户 API Key
        resolved_provider = self._resolve_provider_from_model(model_name) if model_name else None
        api_key = None
        if user_id and resolved_provider:
            api_key = self._get_user_api_key(user_id, resolved_provider)
            if not api_key:
                raise RuntimeError(f"No API key configured for provider '{resolved_provider}'. "
                                   f"Please add your API key in Settings > API Keys.")

        if model_name or api_key:
            self._pipeline = ScriptAnalysisPipeline(model_name=model_name, api_key=api_key)
        # Determine asset type from first item
        asset_type = assets[0].get("type", "other") if assets else "other"
        return await self.pipeline.run_deduplication(assets, asset_type, language)

    async def split_chapters(self,
                             novel_text: str,
                             regex_pattern: Optional[str] = None,
                             use_agent: bool = False,
                             model_name: Optional[str] = None,
                             language: str = "Chinese",
                             user_id: Optional[str] = None) -> List[Dict[str, Any]]:
        """
        Split novel text into chapters.
        Default: Regex-based (reliable for long texts).
        Optional: Agent-based (better for semantic splitting of shorter texts).
        """
        if use_agent:
            # 纯用户密钥模式：获取用户 API Key
            resolved_provider = self._resolve_provider_from_model(model_name) if model_name else None
            api_key = None
            if user_id and resolved_provider:
                api_key = self._get_user_api_key(user_id, resolved_provider)
                if not api_key:
                    raise RuntimeError(f"No API key configured for provider '{resolved_provider}'. "
                                       f"Please add your API key in Settings > API Keys.")

            if model_name or api_key:
                self._pipeline = ScriptAnalysisPipeline(model_name=model_name, api_key=api_key)

            # Use agent
            try:
                res = await self.pipeline.run_chapter_splitting(novel_text, language)
                chapters_data = res.get("chapters", [])

                # 转换 to standard format
                final_chapters = []
                for ch in chapters_data:
                    final_chapters.append({
                        "title": ch.get("title", "Untitled"),
                        "content": ch.get("content", ""),
                        "word_count": len(ch.get("content", "")),
                        "summary": ch.get("summary", "")
                    })
                return final_chapters
            except Exception as e:
                logger.warning(f"Agent-based splitting failed: {e}. Fallback to regex.")
                # Fallback to regex

        return split_chapters(novel_text, regex_pattern)

# Singleton
script_service = ScriptService()
