"""fal.ai 열쇠 확인 + reve/2.1/text-to-image 시험 (2026-08-20).

왜 있나: `.env` 의 `FAL_KEY` 가 아직 쓸 수 있는 열쇠인지 확인하고, 새 모델
`reve/2.1/text-to-image` 를 실제로 불러 본다. 이 저장소의 fal 호출 관례는
`app/services/fal_angle_helpers.py` 에 있고 그것과 같은 모양을 쓴다
(`Authorization: Key <FAL_KEY>`, 동기 `https://fal.run/{모델}`).

★돈: 그림 생성은 **요금이 나간다**. 그래서 두 단계로 나눴다.
    1) `--check` (기본)  — 열쇠만 확인한다. **요금 0**.
    2) `--generate`      — 실제로 한 장 만든다. 요금 발생.
그냥 돌리면 1번만 한다.

쓰는 법:
    cd backend
    .venv/bin/python fal_reve_probe.py                 # 열쇠 확인만(무료)
    .venv/bin/python fal_reve_probe.py --generate      # 그림 1장(유료)
    .venv/bin/python fal_reve_probe.py --generate --prompt "..." --ratio 16:9

산출: artifact/20260820_fal_reve_probe/  (그림 + 응답 원문)

API 규약 출처(2026-08-20 확인):
  · 모델·입력 스키마 https://fal.ai/models/reve/2.1/text-to-image/api
      prompt(필수) · aspect_ratio(기본 auto) · num_images(기본 1)
      · output_format(png|jpeg|webp, 기본 png) · sync_mode
  · REST 규약      https://fal.ai/docs/model-endpoints/queue
      동기 https://fal.run/{모델}  ·  대기열 https://queue.fal.run/{모델}
      머리글 `Authorization: Key $FAL_KEY`
"""
from __future__ import annotations

import argparse
import json
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path

MODEL = "reve/2.1/text-to-image"
SYNC_URL = f"https://fal.run/{MODEL}"
# 열쇠 확인용 — 그림을 만들지 않는 계정 조회 갈래. 요금이 안 나간다.
WHOAMI_URLS = (
    "https://rest.alpha.fal.ai/tokens/",       # 열쇠로 인증되는 계정 자원
    "https://queue.fal.run/health",            # 있으면 통과, 없으면 404
)
OUT_DIR = (Path(__file__).resolve().parent.parent
           / "artifact" / "20260820_fal_reve_probe")

RATIOS = ("4:1", "3:1", "21:9", "2:1", "17:9", "16:9", "3:2", "4:3", "5:4",
          "1:1", "4:5", "3:4", "2:3", "9:16", "1:2", "1:3", "1:4", "auto")


def _key() -> str:
    """설정에서 열쇠를 읽는다 — 이름은 `fal_key` 다(`fal_api_key` 아님)."""
    sys.path.insert(0, str(Path(__file__).resolve().parent))
    from app.core.config import settings

    return settings.fal_key or ""


def _mask(k: str) -> str:
    return f"{k[:10]}…{k[-4:]} (길이 {len(k)})" if k else "(비어 있음)"


def _post(url: str, body: dict, key: str, timeout: int = 180):
    req = urllib.request.Request(
        url, data=json.dumps(body).encode("utf-8"),
        headers={"Authorization": f"Key {key}",
                 "Content-Type": "application/json"})
    with urllib.request.urlopen(req, timeout=timeout) as resp:
        return resp.status, json.loads(resp.read())


def check_key(key: str) -> bool:
    """열쇠 **모양**만 본다 — 살아 있는지는 여기서 못 가린다.

    ★한 번 틀렸다(2026-08-20). 처음에는 인증이 필요한 주소를 GET 해서
    401/403 이 아니면 「인증 통과」로 읽었다. 그런데 그 주소는 **진짜·가짜·빈
    열쇠에 전부 똑같이 405** 를 준다(직접 대조해 확인). 즉 그 확인은 열쇠를
    보지도 않고 「통과」를 찍고 있었다 — 재는 대상이 아닌 것을 재고 있었다.

    fal 은 그림을 만들지 않으면서 열쇠만 가려 주는 공개 주소를 주지 않는다.
    그래서 **진짜 판정은 `--generate` 로 한 번 불러 보는 것**뿐이고, 그때는
    요금이 나간다. 여기서는 모양(비어 있지 않은가, `아이디:비밀` 꼴인가)만
    본다.
    """
    print(f"열쇠: {_mask(key)}")
    if not key:
        print("★ .env 의 FAL_KEY 가 비어 있다.")
        return False
    if ":" not in key:
        print("※ 참고: fal 열쇠는 보통 `아이디:비밀` 꼴이다 — 지금은 아니다.")
    print("  모양은 갖췄다. ※ 살아 있는지는 여기서 못 가린다 — "
          "요금 없이 가려 주는 주소가 없다(405 로만 답한다).")
    return True


def generate(key: str, prompt: str, ratio: str, fmt: str) -> int:
    """실제로 한 장 만든다 — **요금이 나간다.**"""
    if ratio not in RATIOS:
        print(f"★ 화면 비율은 {RATIOS} 중 하나여야 한다: {ratio}")
        return 2
    body = {"prompt": prompt, "aspect_ratio": ratio,
            "num_images": 1, "output_format": fmt}
    print(f"\n보낸다 → {SYNC_URL}")
    print(f"  본문: {json.dumps(body, ensure_ascii=False)}")

    t0 = time.time()
    try:
        status, result = _post(SYNC_URL, body, key)
    except urllib.error.HTTPError as exc:
        raw = exc.read()[:1500].decode(errors="replace")
        print(f"★ HTTP {exc.code}\n{raw}")
        return 1
    except Exception as exc:  # noqa: BLE001
        print(f"★ 실패: {type(exc).__name__} {exc}")
        return 1
    elapsed = time.time() - t0

    OUT_DIR.mkdir(parents=True, exist_ok=True)
    (OUT_DIR / "response.json").write_text(
        json.dumps(result, ensure_ascii=False, indent=1), encoding="utf-8")

    images = result.get("images") or []
    print(f"\nHTTP {status} · {elapsed:.1f}초 · 그림 {len(images)}장")
    if not images:
        print("★ 응답에 그림이 없다. 원문은 response.json 참조.")
        return 1

    im = images[0]
    print(f"  크기 {im.get('width')}×{im.get('height')} "
          f"· 형식 {im.get('content_type')} · 바이트 {im.get('file_size')}")
    url = im.get("url", "")
    if not url:
        print("★ 그림 주소가 없다.")
        return 1

    with urllib.request.urlopen(url, timeout=60) as dl:
        data = dl.read()
    path = OUT_DIR / f"reve21_{ratio.replace(':', 'x')}.{fmt}"
    path.write_bytes(data)
    print(f"  받음: {path}  ({len(data):,} 바이트)")
    if "seed" in result:
        print(f"  씨앗: {result['seed']}")
    return 0


def main() -> int:
    ap = argparse.ArgumentParser(description=__doc__)
    ap.add_argument("--generate", action="store_true",
                    help="실제로 그림을 만든다 — 요금이 나간다")
    ap.add_argument("--prompt", default=(
        "A weathered wooden fishing boat resting on a pebble shore at dawn, "
        "soft mist over calm water, muted blue-grey palette, natural light"),
        help="그릴 내용(영어)")
    ap.add_argument("--ratio", default="16:9", help=f"화면 비율 {RATIOS}")
    ap.add_argument("--format", default="png", choices=("png", "jpeg", "webp"))
    args = ap.parse_args()

    key = _key()
    ok = check_key(key)
    if not ok:
        return 1
    if not args.generate:
        print("\n열쇠는 쓸 수 있다. 그림까지 만들려면 --generate 를 붙인다"
              " (요금 발생).")
        return 0
    return generate(key, args.prompt, args.ratio, args.format)


if __name__ == "__main__":
    raise SystemExit(main())
