"""런타임 스틸 파이프라인 설정 API (2026-08-13 #108).

i2i 시네마틱 변환 스테이지(on/off)를 UI 에서 전환한다:
  · 프로세스 settings 즉시 반영 — 다음 스텝 실행부터 유효
    (스텝 config_hash 는 ON 일 때만 스탬프되는 관례 그대로).
  · backend/.env 영속 — 재기동 생존. 쓰기는 원자적(임시 파일→rename)
    이고 대상 키 한 줄만 바꾼다(다른 줄 byte 보존 — 추가 전용 관례).
"""
from __future__ import annotations

import logging
import re
import threading
from pathlib import Path
from typing import Optional

from fastapi import APIRouter, Depends
from pydantic import BaseModel

from app.api.deps import get_current_user, require_admin
from app.core.config import settings

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/v1/settings", tags=["settings"])

# backend/app/api/v1/settings.py → parents[3] == backend/ (Settings 의
# env_file=".env" 이 읽는 그 파일 — 기동 CWD=backend 관례).
_ENV_PATH = Path(__file__).resolve().parents[3] / ".env"

# Codex R2 BLOCK-3: 동시 PUT 2건이 같은 .env.tmp 를 덮고 rename 하면
# 성공 응답 뒤 runtime 과 .env 가 반대로 남을 수 있다 — 영속+런타임
# 변경을 프로세스 내 직렬화. (현 배포=uvicorn 단일 worker. 다중 worker
# 로 가면 파일 락(fcntl)으로 확장할 것 — 그때까지 이 락이 SOT.)
_TOGGLE_LOCK = threading.Lock()


class StillPipelineSettings(BaseModel):
    still_cine_transform_enabled: bool
    still_image_backend: str
    cine_transform_pack: str


class StillPipelineUpdate(BaseModel):
    still_cine_transform_enabled: bool


def _snapshot() -> StillPipelineSettings:
    from app.modules.pipeline.still_recipe import (
        CINE_TRANSFORM_PROMPT_VERSION,
        resolve_prompt_version,
    )

    return StillPipelineSettings(
        still_cine_transform_enabled=bool(
            getattr(settings, "still_cine_transform_enabled", False)),
        still_image_backend=str(
            getattr(settings, "still_image_backend", "nb2")),
        cine_transform_pack=resolve_prompt_version(
            CINE_TRANSFORM_PROMPT_VERSION),
    )


def persist_env_flag(
    key: str, value: str, env_path: Optional[Path] = None,
) -> None:
    """.env 의 `KEY=` 줄만 교체(전부)·없으면 끝에 추가 — 원자 쓰기.

    다른 줄은 byte 그대로 보존한다. 파일 부재 시 새로 만들지 않고
    RuntimeError — 운영 .env 가 있어야 할 자리에 없다는 것은 배선
    사고라 조용히 새 파일을 만들면 원인이 숨는다.
    """
    path = env_path or _ENV_PATH
    if not path.exists():
        raise RuntimeError(f".env 가 없다: {path} — 영속 대상 부재")
    lines = path.read_text(encoding="utf-8").splitlines(keepends=True)
    pat = re.compile(rf"^{re.escape(key)}=")
    out = []
    replaced = False
    for ln in lines:
        if pat.match(ln):
            out.append(f"{key}={value}\n")
            replaced = True
        else:
            out.append(ln)
    if not replaced:
        if out and not out[-1].endswith("\n"):
            out[-1] += "\n"
        out.append(f"{key}={value}\n")
    tmp = path.parent / (path.name + ".tmp")
    tmp.write_text("".join(out), encoding="utf-8")
    tmp.replace(path)


@router.get("/still-pipeline", response_model=StillPipelineSettings)
def get_still_pipeline_settings(
    _user=Depends(get_current_user),
) -> StillPipelineSettings:
    return _snapshot()


@router.put("/still-pipeline", response_model=StillPipelineSettings)
def update_still_pipeline_settings(
    body: StillPipelineUpdate,
    # Codex R2 BLOCK-3: 전역 유료 설정 — 일반 creator 가 전 프로젝트의
    # 다음 실행에 grok 비용을 켤 수 있으면 안 된다. admin 전용.
    _user=Depends(require_admin),
) -> StillPipelineSettings:
    enabled = bool(body.still_cine_transform_enabled)
    # Codex R1 BLOCK-4: 영속 성공 **후에** 런타임을 바꾼다 — 역순이면
    # .env 쓰기 실패(500)를 UI 는 "변경 실패"로 읽는데 프로세스 플래그는
    # 이미 ON 이라 다음 실행부터 유료 변환이 나간다(돈 결함).
    # R2 BLOCK-3: 영속+런타임을 락으로 직렬화(동시 PUT 역전 창 제거).
    with _TOGGLE_LOCK:
        persist_env_flag(
            "STILL_CINE_TRANSFORM_ENABLED", "true" if enabled else "false")
        settings.still_cine_transform_enabled = enabled
    logger.info(
        "settings: still_cine_transform_enabled=%s (UI 토글, .env 영속)",
        enabled,
    )
    return _snapshot()
