"""capture sink — 생성 이미지/아티팩트를 enqueue (Task A5).

저수준 생성 사이트(Phase B 에서 계측)가 호출한다. DB 를 직접 모르고, 현재
generation_context(ambient)가 있으면 spool 파일에 기록 후 그 scope 의 큐에
append 만 한다. flush(DB insert)는 scope 종료 시 queue.flush() 가 담당.

★ default-capture-off (중복 0 의 핵심): generation_context scope 가 열리지 않은
호출(=최종물 경로 등)은 ctx 가 None → 저장하지 않고 diagnostic counter 만 증가.
최종물은 기존 _register_image_assets 가 image_asset 을 만들므로 sink 가 또 만들면
중복이 된다. 따라서 sink 는 명시적으로 scope 가 열린 중간물만 포착한다.

모든 동작 non-fatal — 어떤 실패도 raise 하지 않는다(생성 파이프라인 무영향).
"""

from __future__ import annotations

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

from app.services.image_capture.context import current_context
from app.services.image_capture.queue import CAPTURE_DIAG
from app.services.image_capture.spool import write_spool

logger = logging.getLogger(__name__)


def _enqueue(png_bytes: bytes, meta: Dict[str, Any]) -> None:
    """공통 enqueue — ctx 없으면 skip(default-off), 있으면 spool + append. non-fatal."""
    try:
        ctx = current_context()
        if ctx is None:
            CAPTURE_DIAG["skipped_no_context"] += 1
            return
        spool_path = write_spool(png_bytes, ctx.project_id, ctx.stage)
        ctx.queue.append(spool_path, meta)
    except Exception:  # 어떤 실패도 생성 파이프라인을 막지 않는다.
        CAPTURE_DIAG["flush_errors"] += 1
        logger.warning("image_capture sink failed (non-fatal)", exc_info=True)


def capture_generated_image(
    png_bytes: bytes,
    *,
    role: str,
    input_image_ids: Optional[List[str]] = None,
    candidate_index: Optional[int] = None,
    generation_call_id: Optional[str] = None,
    prompt: Optional[str] = None,
    pipeline_metadata: Optional[Dict[str, Any]] = None,
    disposition: Optional[str] = None,
    attempt_index: Optional[int] = None,
) -> None:
    """모델이 생성한 이미지 바이트를 (scope 가 열려 있을 때만) 포착.

    ``disposition`` — 산출물 생애 결과 (accepted|rejected|diagnostic|
    cache_hit_source). None=미상. ``attempt_index`` — 재시도 회차(0-base).
    """
    _enqueue(
        png_bytes,
        {
            "pipeline_role": role,
            "input_image_ids": input_image_ids,
            "candidate_index": candidate_index,
            "generation_call_id": generation_call_id,
            "prompt": prompt,
            "pipeline_metadata": pipeline_metadata,
            "disposition": disposition,
            "attempt_index": attempt_index,
        },
    )


def capture_artifact(png_bytes: bytes, *, role: str, **kw: Any) -> None:
    """비모델(PIL/결정론) 아티팩트 바이트를 (scope 가 열려 있을 때만) 포착.

    capture_generated_image 와 동일 경로 — role + 선택 메타(input_image_ids/
    candidate_index/generation_call_id/prompt/pipeline_metadata/disposition/
    attempt_index)를 받는다. 비모델 진단 아티팩트는 보통 ``disposition='diagnostic'``.
    """
    _enqueue(
        png_bytes,
        {
            "pipeline_role": role,
            "input_image_ids": kw.get("input_image_ids"),
            "candidate_index": kw.get("candidate_index"),
            "generation_call_id": kw.get("generation_call_id"),
            "prompt": kw.get("prompt"),
            "pipeline_metadata": kw.get("pipeline_metadata"),
            "disposition": kw.get("disposition"),
            "attempt_index": kw.get("attempt_index"),
        },
    )
