"""아웃룩 추출 — 전체 씬 + 캐릭터 목록 → 아웃룩 전체 매핑 (1회 호출).

Gemini 3.1 Pro에 전체 씬 JSON + 캐릭터 목록을 보내서
모든 아웃룩을 한번에 추출하고, 씬별 캐릭터-아웃룩 연결을 생성.
결과는 JSON으로 저장.
"""

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

from app.core.config import settings
from app.modules.prompt_loader import load_prompt, load_schema as _loader_schema

logger = logging.getLogger(__name__)

_MODULE = "outlook_extractor"


def _load_prompt(**kwargs) -> str:
    return load_prompt(_MODULE, "extract_prompt", **kwargs)


def _load_schema() -> Dict[str, Any]:
    return _loader_schema(_MODULE, "extract_schema")


def extract_all_outlooks(
    segments: List[Dict[str, Any]],
    fulltext: str,
    characters: List[str],
    char_sid_list: Optional[List[Dict[str, str]]] = None,
    checkpoint_dir: Optional[str] = None,
    project_llm_config: Optional[Dict[str, Any]] = None,
    visual_world_rules: Optional[List[str]] = None,
    scene_present_characters: Optional[Dict] = None,
) -> Dict[str, Any]:
    """전체 씬 + 캐릭터 → 아웃룩 전체 매핑.

    Args:
        segments: 분할된 씬 목록
        fulltext: 시나리오 전문
        characters: 확정된 캐릭터 이름 목록
        char_sid_list: [{id: "C01", name: "동녘"}, ...] — short_id 기반

    Returns:
        {
            "outlooks": [{name, description, is_shared}],
            "scene_assignments": [{scene_index, characters: [{character_id|character_name, outlook_name}]}]
        }
    """
    # 씬 JSON 구성 (씬 텍스트 + 물리적 존재 인물 포함)
    scenes_json = []
    for seg in segments:
        scene_data = {
            "scene_index": seg["scene_index"],
            "heading": seg.get("heading", ""),
            "text": seg.get("text", ""),
        }
        if scene_present_characters:
            present = scene_present_characters.get(str(seg["scene_index"]),
                      scene_present_characters.get(seg["scene_index"], []))
            if present:
                scene_data["physically_present_characters"] = present
        scenes_json.append(scene_data)

    # short_id 기반 캐릭터 목록 (있으면 사용)
    if char_sid_list:
        character_list = "\n".join(f"- {c['id']} ({c['name']})" for c in char_sid_list)
    else:
        character_list = "\n".join(f"- {name}" for name in characters)
    scenes_json_str = json.dumps(scenes_json, ensure_ascii=False, indent=1)

    prompt = _load_prompt(
        character_list=character_list,
        scenes_json=scenes_json_str,
    )
    schema = _load_schema()

    # schema에 character_id enum 주입 (short_id 기반)
    if char_sid_list:
        valid_char_ids = [c["id"] for c in char_sid_list]
        schema = json.loads(json.dumps(schema))  # deep copy
        char_items = schema["properties"]["scene_assignments"]["items"]["properties"]["characters"]["items"]["properties"]
        if "character_id" in char_items:
            char_items["character_id"]["enum"] = valid_char_ids

    # LLM 클라이언트를 통해 호출 (프로젝트 설정에 따라 provider/model 선택)
    from app.modules.llm.llm_client import call_structured
    logger.info("Outlook extraction: %d scenes, %d characters",
                len(segments), len(characters))

    system_instruction = "영화 의상 담당자. 각 인물의 복장을 정확히 구분하고 시각적으로 상세히 디자인한다."
    if visual_world_rules:
        rules_text = "\n".join(f"- {r}" for r in visual_world_rules)
        system_instruction += f"\n\n[시각적 세계관 규칙 — 인물 물리적 존재 판단 시 반드시 참고]\n{rules_text}"
    result = None
    for retry in range(3):
        try:
            result = call_structured(
                step="outlook_extraction",
                system_prompt=system_instruction,
                user_prompt=prompt,
                response_schema=schema,
                project_config=project_llm_config,
                schema_name="outlook_extraction",
            )
            break
        except Exception as exc:
            logger.warning("Outlook extraction attempt %d failed: %s", retry + 1, exc)
            if retry < 2:
                import time
                time.sleep(5 * (retry + 1))

    if not result:
        logger.error("Outlook extraction failed after all retries")
        return {"outlooks": [], "scene_assignments": []}

    outlooks = result.get("outlooks", [])
    assignments = result.get("scene_assignments", [])

    # character_id(short_id) → character_name 역매핑 (하위 호환)
    if char_sid_list:
        sid_to_name = {c["id"]: c["name"] for c in char_sid_list}
        for sa in assignments:
            for c in sa.get("characters", []):
                cid = c.get("character_id", "")
                if cid and "character_name" not in c:
                    c["character_name"] = sid_to_name.get(cid, cid)

    logger.info("Outlook extraction complete: %d outlooks, %d scene assignments",
                len(outlooks), len(assignments))

    return result
