"""기획서 분석 결과를 파이프라인 step 프롬프트에 주입하는 헬퍼.

사용법:
    from app.core.planning_doc_context import get_planning_context
    ctx = get_planning_context(project_id, episode_id, self.db)
    if ctx.characters_text:
        prompt += f"\n\n## 기획서 인물 정보\n{ctx.characters_text}"
"""

import json
import logging
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional

from app.core.config import settings

logger = logging.getLogger(__name__)


@dataclass
class PlanningContext:
    """기획서에서 추출된 주입 가능한 컨텍스트."""
    has_planning_doc: bool = False
    is_first_episode: bool = False

    # 섹션별 텍스트 (빈 문자열 = 해당 섹션 없음)
    characters_text: str = ""
    # ★장소 (2026-09-18) — 인물과 같은 모양. 인물과 달리 **화 제한이 없다**:
    #  장소는 한 화에 매인 것이 아니라 작품 전체에 걸친 사실이다.
    locations_text: str = ""
    world_setting: str = ""
    tone_mood: str = ""
    story_arc: str = ""
    visual_concepts: str = ""
    relationships_text: str = ""

    # available 섹션 목록
    available_sections: list = field(default_factory=list)

    def inject_if_available(self, section: str, header: str = "") -> str:
        """섹션이 있으면 헤더+내용 반환, 없으면 빈 문자열.

        첫 에피소드만 주입하는 섹션(characters)은 is_first_episode 체크.
        """
        # characters는 첫 에피소드에서만 주입
        if section == "characters_text" and not self.is_first_episode:
            return ""
        if section == "relationships_text" and not self.is_first_episode:
            return ""

        val = getattr(self, section, "")
        if not val:
            return ""
        hdr = header or f"## 기획서 참고: {section}"
        return (
            f"\n\n{hdr}\n"
            f"<planning_doc_reference>\n{val}\n</planning_doc_reference>"
        )


def get_planning_context(project_id: str, episode_id: str, db=None) -> PlanningContext:
    """프로젝트의 기획서 분석 체크포인트를 로드하여 PlanningContext 반환.

    Lookup 순서:
      1. project-level: ``planning_doc_analysis_service.load_project_checkpoint``
         (L4 source_hash 검증 통과한 데이터만 — stale resurrection 차단).
      2. legacy episode-level: ``checkpoints/episodes/{eid}/planning_doc_analysis/manifest.json``
         (구 PlanningDocAnalysisStep cascade 산물 — 옛 schema, raw read OK).

    db: 호출자의 SQLAlchemy 세션. None이면 새 세션 생성.
    """
    ctx = PlanningContext()

    # 1) project-level — 검증된 reader 위임 (source_hash mismatch 면 None).
    from app.services.planning_doc_analysis_service import load_project_checkpoint
    data: Optional[dict] = load_project_checkpoint(project_id, db=db)

    # 2) legacy episode-level fallback — 옛 cascade 산물 (source_hash 없는 schema)
    if data is None:
        base = (
            Path(settings.projects_dir)
            / project_id
            / "checkpoints"
            / "episodes"
        )
        legacy_cp: Optional[Path] = None
        ep_cp = base / episode_id / "planning_doc_analysis" / "manifest.json"
        if ep_cp.exists():
            legacy_cp = ep_cp
        elif base.exists():
            for ep_dir in sorted(base.iterdir()):
                alt = ep_dir / "planning_doc_analysis" / "manifest.json"
                if alt.exists():
                    legacy_cp = alt
                    break
        if legacy_cp is None:
            return ctx
        try:
            raw = json.loads(legacy_cp.read_text(encoding="utf-8"))
        except Exception as exc:
            logger.warning("Failed to load legacy planning doc checkpoint: %s", exc)
            return ctx
        data = raw.get("data", raw) if isinstance(raw, dict) else None

    if not isinstance(data, dict):
        return ctx

    available = data.get("available_sections", [])
    if not available:
        return ctx

    ctx.has_planning_doc = True
    ctx.available_sections = available

    # 첫 에피소드 판별
    from app.models.project import Episode
    should_close = False
    if db is None:
        from app.core.database import SessionLocal
        db = SessionLocal()
        should_close = True
    try:
        episodes = (
            db.query(Episode)
            .filter(Episode.project_id == project_id)
            .order_by(Episode.episode_number)
            .all()
        )
        if episodes and episodes[0].id == episode_id:
            ctx.is_first_episode = True
    finally:
        if should_close:
            db.close()

    # 인물 정보 → 텍스트
    characters = data.get("characters", [])
    if characters and "characters" in available:
        lines = []
        for c in characters:
            parts = [f"- **{c['name']}**"]
            if c.get("age_gender"):
                parts.append(f"({c['age_gender']})")
            if c.get("role"):
                parts.append(f"[{c['role']}]")
            parts.append(f": {c['description']}")
            if c.get("visual_traits"):
                parts.append(f"\n  외형: {c['visual_traits']}")
            lines.append(" ".join(parts))
        ctx.characters_text = "\n".join(lines)

    # 장소 정보 → 텍스트 (인물과 같은 조립)
    locations = data.get("locations", [])
    if locations and "locations" in available:
        lines = []
        for loc in locations:
            parts = [f"- **{loc['name']}**", f": {loc['description']}"]
            if loc.get("visual_traits"):
                parts.append(f"\n  외형: {loc['visual_traits']}")
            lines.append(" ".join(parts))
        ctx.locations_text = "\n".join(lines)

    # 단순 텍스트 섹션
    if "world_setting" in available:
        ctx.world_setting = data.get("world_setting", "")
    if "tone_mood" in available:
        ctx.tone_mood = data.get("tone_mood", "")
    if "story_arc" in available:
        ctx.story_arc = data.get("story_arc", "")
    if "visual_concepts" in available:
        ctx.visual_concepts = data.get("visual_concepts", "")

    # 인물 관계
    rels = data.get("key_relationships", [])
    if rels and "key_relationships" in available:
        ctx.relationships_text = "\n".join(
            f"- {r['characters']}: {r['relationship']}" for r in rels
        )

    return ctx
