"""④ FastAPI orchestration — LLM → Higgsfield → Remotion wrapper.

POST /api/generate     (multipart: photos[] + interview fields) → job_id 즉시 반환
    백그라운드 워커: build_spec (OpenAI) → higgsfield.generate → remotion.render → mp4
GET  /api/jobs/{id}    작업 상태 폴링 (queued | running | done | error)
GET  /api/outputs      완료된 영상 목록 (갤러리)
GET  /api/health
Static: /outputs/<file>  (final videos),  /  (the web app)
"""
from __future__ import annotations

import json
import shutil
import uuid
from pathlib import Path

from fastapi import FastAPI, File, Form, UploadFile, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles

from app import config as cfg, jobs
from app.schemas import GenerateRequest, JobStatus, ScriptResult, VideoMeta
from app.pipeline import spec_builder, azure_storage

WEBAPP = cfg.WEBAPP_DIR
OUTPUTS = cfg.OUTPUTS_DIR
UPLOADS = cfg.UPLOADS_DIR
OUTPUTS.mkdir(parents=True, exist_ok=True)

app = FastAPI(title="ADO2 Higgsfield Shorts")
app.add_middleware(
    CORSMiddleware, allow_origins=cfg.CORS_ORIGINS, allow_methods=["*"], allow_headers=["*"],
)


@app.get("/api/health")
def health():
    return {"ok": True}


@app.get("/api/outputs", response_model=list[VideoMeta])
def list_outputs():
    """완료된 영상 목록을 최신순으로 반환.

    Azure 설정이 있으면 Azure Blob 목록을 조회하고,
    로컬 JSON 에서 메타데이터(업체명·캡션·프로파일)를 보완한다.
    Azure 미설정 시 로컬 outputs/ 디렉터리를 직접 스캔한다.
    """
    # ── Azure 우선 ──
    if cfg.azure_enabled():
        try:
            blobs = azure_storage.list_videos()   # [{job_id, video_url, created_at}]
            results: list[VideoMeta] = []
            for b in blobs:
                job_id = b["job_id"]
                meta_file = OUTPUTS / f"{job_id}.json"
                if meta_file.exists():
                    try:
                        data = json.loads(meta_file.read_text(encoding="utf-8"))
                        data["video_url"] = b["video_url"]   # Azure URL 우선
                        results.append(VideoMeta(**data))
                        continue
                    except Exception:
                        pass
                results.append(VideoMeta(
                    job_id=job_id,
                    video_url=b["video_url"],
                    created_at=b["created_at"],
                ))
            return results
        except Exception as e:
            # Azure 조회 실패 시 로컬 fallback
            print(f"[outputs] Azure 목록 조회 실패, 로컬 fallback: {e}")

    # ── 로컬 fallback ──
    OUTPUTS.mkdir(parents=True, exist_ok=True)
    results = []
    for mp4 in sorted(OUTPUTS.glob("*.mp4"), key=lambda p: p.stat().st_mtime, reverse=True):
        job_id = mp4.stem
        meta_file = OUTPUTS / f"{job_id}.json"
        if meta_file.exists():
            try:
                data = json.loads(meta_file.read_text(encoding="utf-8"))
                results.append(VideoMeta(**data))
                continue
            except Exception:
                pass
        results.append(VideoMeta(
            job_id=job_id,
            video_url=f"/outputs/{job_id}.mp4",
            created_at=mp4.stat().st_mtime,
        ))
    return results


@app.post("/api/captions", response_model=ScriptResult)
def captions(req: GenerateRequest):
    """인터뷰 답변 → 인트로·셀링포인트·감성스토리·CTA 4블록 (OpenAI)."""
    try:
        return spec_builder.build_script(req)
    except Exception as e:
        raise HTTPException(502, f"자막 생성 실패: {e}") from e


@app.post("/api/generate", response_model=JobStatus, status_code=202)
def generate(  # 논블로킹: 사진만 저장하고 작업을 큐에 넣은 뒤 job_id 반환
    photos: list[UploadFile] = File(...),
    kind: str = Form(...),
    biz_name: str = Form(...),
    selling: str = Form(...),
    addr: str | None = Form(None),
    price: str | None = Form(None),
    dry_run: bool = Form(False),
):
    if len(photos) < 4:
        raise HTTPException(400, "사진 4장 이상이 필요합니다.")

    # 업로드 사진을 디스크에 저장 (워커 스레드가 경로로 읽음).
    # UploadFile 의 스트림은 요청 종료 후 닫히므로 반드시 여기서 영속화한다.
    upload_id = uuid.uuid4().hex[:8]
    job_dir = UPLOADS / upload_id
    job_dir.mkdir(parents=True, exist_ok=True)
    photo_paths: list[Path] = []
    for i, up in enumerate(photos):
        # 원본 파일명에 한글·유니코드 문자가 있으면 fal 업로드 클라이언트가 ASCII 에러를 냄.
        # 확장자만 보존하고 나머지는 순수 숫자로 대체한다.
        suffix = Path(up.filename or "img.jpg").suffix.lower() or ".jpg"
        dest = job_dir / f"{i:02d}{suffix}"
        with dest.open("wb") as f:
            shutil.copyfileobj(up.file, f)
        photo_paths.append(dest)

    req = GenerateRequest(kind=kind, biz_name=biz_name, addr=addr, price=price, selling=selling)

    # 파이프라인은 백그라운드 워커에서 실행 → 즉시 queued 상태를 돌려준다.
    job_id = jobs.submit(req, photo_paths, dry_run=dry_run)
    status = jobs.get(job_id)
    return JobStatus(**{k: status[k] for k in JobStatus.model_fields})


@app.get("/api/jobs/{job_id}", response_model=JobStatus)
def job_status(job_id: str):
    """작업 진행 상태 폴링. 프론트가 주기적으로 호출."""
    status = jobs.get(job_id)
    if status is None:
        raise HTTPException(404, "작업을 찾을 수 없습니다(만료되었거나 잘못된 ID).")
    return JobStatus(**{k: status[k] for k in JobStatus.model_fields})


# Static mounts (after API routes)
app.mount("/outputs", StaticFiles(directory=str(OUTPUTS)), name="outputs")


@app.get("/")
def index():
    return FileResponse(str(WEBAPP / "index.html"))


app.mount("/", StaticFiles(directory=str(WEBAPP)), name="webapp")
