"""Step Read-Model Service — `/steps` read view 계산.

W1-F11: `api/v1/steps.py.get_all_steps` 본문을 이관. route는 이 함수 호출 한 줄.

책임:
- step_run DB 조회
- ordered step catalog 순회
- gate/blocked 판정
- model override 해석
- consumed_by(drift stale 표시용) 조회
- 응답 dict 구성

비책임:
- HTTP 파싱 / response envelope / auth — route 담당
"""
from __future__ import annotations

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

from sqlalchemy import text
from sqlalchemy.orm import Session as OrmSession

from app.core.step_catalog import get_consumers_of, get_ordered_entries

logger = logging.getLogger(__name__)


def _load_project_config(db: OrmSession, project_id: str) -> Dict[str, Any]:
    """ProjectSettings.llm_config_json → dict. 파싱 실패 시 {} + warn."""
    from app.models.project import ProjectSettings

    ps = db.query(ProjectSettings).filter(ProjectSettings.project_id == project_id).first()
    if not ps or not ps.llm_config_json:
        return {}
    try:
        return json.loads(ps.llm_config_json)
    except Exception as exc:
        logger.warning(
            "llm_config_json parse failed for project %s: %s — 기본 {} 사용",
            project_id, exc,
        )
        return {}


def _load_step_run_map(db: OrmSession, project_id: str, episode_id: str) -> Dict[str, Dict[str, Any]]:
    """step_run 테이블에서 에피소드별 단일 조회 후 step_id→row 매핑.

    W4 P3-4: sync_status/sync_error/synced_at 컬럼도 포함 (P3-2/P3-3 관측성 UI 연동).
    """
    runs = db.execute(text(
        "SELECT step_id, status, resolved_model, completed_count, applicable_count, "
        "failed_count, started_at, completed_at, error_message, "
        "sync_status, sync_error, synced_at "
        "FROM step_run WHERE project_id = :pid AND episode_id = :eid"
    ), {"pid": project_id, "eid": episode_id}).fetchall()
    return {r[0]: {
        "status": r[1], "resolved_model": r[2],
        "completed_count": r[3], "applicable_count": r[4],
        "failed_count": r[5], "started_at": r[6],
        "completed_at": r[7], "error_message": r[8],
        "sync_status": r[9], "sync_error": r[10], "synced_at": r[11],
    } for r in runs}


def get_all_steps_view(
    db: OrmSession,
    project_id: str,
    episode_id: str,
) -> List[Dict[str, Any]]:
    """전체 단계 read-model. route의 `get_all_steps`가 이것만 호출."""
    from app.modules.llm.llm_client import _resolve_model

    project_config = _load_project_config(db, project_id)
    run_map = _load_step_run_map(db, project_id, episode_id)

    steps: List[Dict[str, Any]] = []
    for entry in get_ordered_entries():
        sid = entry.step_id
        run = run_map.get(sid, {})
        status = run.get("status", "pending")

        # 의존 단계 확인 → blocked/runnable 판단
        deps = entry.depends_on
        can_run = True
        blocked_by: List[str] = []
        for dep in deps:
            dep_status = run_map.get(dep, {}).get("status", "pending")
            if dep_status not in ("completed", "not_applicable", "partial"):
                can_run = False
                blocked_by.append(dep)

        if status == "pending" and not can_run:
            status = "blocked"

        model = _resolve_model(sid, project_config)

        # consumed_by: 이 step을 역참조 소비하는 상류 step 목록 (2-pass 의존성).
        # status=stale + consumed_by 비어있지 않음 = drift stale (상류 force 이후 재실행 필요).
        # 일반 stale(상류 변경으로 하류가 stale됨)과 구분해 UI에서 경고 강조 가능.
        consumed_by = get_consumers_of(sid)

        step_data: Dict[str, Any] = {
            "step_id": sid,
            "label": entry.label,
            "category": entry.category,
            "order": entry.order,
            "provider": entry.provider,
            "model": model,
            "depends_on": list(deps),
            "fan_out": entry.fan_out,
            "applicability": entry.applicability,
            "status": status,
            "can_run": can_run,
            "blocked_by": blocked_by,
            "consumed_by": consumed_by,
            "completed_count": run.get("completed_count"),
            "applicable_count": run.get("applicable_count"),
            "failed_count": run.get("failed_count"),
            "started_at": run.get("started_at"),
            "completed_at": run.get("completed_at"),
            "error_message": run.get("error_message"),
            # W4 P3-4: sync 관측성 필드 — FE가 Repair 버튼/뱃지 렌더링에 사용
            "sync_status": run.get("sync_status"),
            "sync_error": run.get("sync_error"),
            "synced_at": run.get("synced_at"),
        }

        if entry.sub_steps:
            step_data["sub_steps"] = entry.sub_steps

        steps.append(step_data)

    return steps
