"""W21B-w5 — bg_space_partition edge-judge LLM provider.

Thin LLM IO boundary for the pass-2 edge judge. The pure module
(``bg_space_partition.py``) owns candidate generation, prompt assembly, the
R2/R3 gates and validation; this module only turns a built prompt bundle into a
model response. Text LLM only (no VLM re-call — the projection cards already
carry the VLM-read ``visible_items``).

Mirrors the fail-closed discipline of ``shot_projection_card_provider`` /
``shot_aware_bg_render_plan_llm_provider``: ``litellm`` is lazy-imported inside
the function so importing this module touches no SDK, every preflight runs
before any network call, exactly one ``litellm.completion`` is issued with
``num_retries=0`` and no temperature, and the response is guarded at every
joint. No scenario tokens live here — the system / schema come from the pure
module's ``build_edge_judge_prompt`` bundle.
"""
from __future__ import annotations

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

logger = logging.getLogger(__name__)

# NOTE: 'gpt55' 는 역사적 안정 식별자(provenance 매칭/lineage 호환) — 모델 교체와
# 무관하게 유지. 실제 물리 모델은 별도 model 인자/PROVIDER_MODEL_DEFAULT 에 기록되며
# 이 식별자에서 추론하지 않는다.
WIRED_PROVIDER_NAME: str = "litellm_gpt55_bg_space_edge_judge_v1"
PROVIDER_MODEL_DEFAULT: str = "openai/gpt-5.6-sol"
MAX_COMPLETION_TOKENS_DEFAULT: int = 4000
TIMEOUT_SECONDS_DEFAULT: int = 180
RESPONSE_SCHEMA_NAME: str = "bg_space_edge_judge"


class EdgeJudgeProviderError(Exception):
    """Fail-closed signal for the W21B-w5 edge-judge provider path."""


def litellm_edge_judge_provider(
    *,
    prompt_bundle: Dict[str, Any],
    model: str = PROVIDER_MODEL_DEFAULT,
    max_completion_tokens: int = MAX_COMPLETION_TOKENS_DEFAULT,
    timeout_seconds: int = TIMEOUT_SECONDS_DEFAULT,
) -> Dict[str, Any]:
    """Adjudicate one candidate edge via a single litellm text call.

    ``prompt_bundle`` is exactly what ``build_edge_judge_prompt`` assembled
    (``{system, user, schema}``). Returns the raw parsed JSON dict; the pure
    module's ``validate_edge_judge_output`` canonicalises and gates it. The
    completion call count stays 0 whenever the helper fail-closes during
    preflight.
    """
    if not isinstance(prompt_bundle, dict):
        raise EdgeJudgeProviderError("prompt_bundle must be a dict")
    system = prompt_bundle.get("system")
    user = prompt_bundle.get("user")
    schema = prompt_bundle.get("schema")
    if not isinstance(system, str) or not system:
        raise EdgeJudgeProviderError("prompt_bundle.system missing or non-string")
    if not isinstance(user, str) or not user:
        raise EdgeJudgeProviderError("prompt_bundle.user missing or non-string")
    if not isinstance(schema, dict) or not schema:
        raise EdgeJudgeProviderError("prompt_bundle.schema missing or non-dict")

    from app.core.openai_keys import has_openai_key
    if not has_openai_key():
        raise EdgeJudgeProviderError(
            "OPENAI_API_KEY missing or empty; refusing to call litellm"
        )

    try:
        from app.core.openai_keys import (  # type: ignore
            llm_completion as _llm_completion,
        )
    except Exception as exc:  # pragma: no cover — env-dependent
        raise EdgeJudgeProviderError(
            f"litellm import failed: {type(exc).__name__}: {exc}"
        ) from exc

    response_format = {
        "type": "json_schema",
        "json_schema": {
            "name": RESPONSE_SCHEMA_NAME,
            "strict": True,
            "schema": schema,
        },
    }

    try:
        response = _llm_completion(
            model=model,
            messages=[
                {"role": "system", "content": system},
                {"role": "user", "content": user},
            ],
            response_format=response_format,
            timeout=timeout_seconds,
            max_completion_tokens=max_completion_tokens,
            num_retries=0,
        )
    except Exception as exc:
        raise EdgeJudgeProviderError(
            f"litellm.completion raised: {type(exc).__name__}: {exc}"
        ) from exc

    choices = getattr(response, "choices", None) or []
    if not choices:
        raise EdgeJudgeProviderError("response.choices is empty")
    choice = choices[0]
    msg = getattr(choice, "message", None)
    if msg is None:
        raise EdgeJudgeProviderError("response.choices[0].message is missing")
    if getattr(msg, "refusal", None):
        raise EdgeJudgeProviderError(
            f"judge emitted a refusal: {str(getattr(msg, 'refusal'))[:200]!r}"
        )
    content = getattr(msg, "content", None)
    if not content:
        raise EdgeJudgeProviderError("response.choices[0].message.content is empty")
    if getattr(choice, "finish_reason", None) != "stop":
        raise EdgeJudgeProviderError(
            f"finish_reason must be 'stop' exactly "
            f"(got {getattr(choice, 'finish_reason', None)!r})"
        )

    try:
        return json.loads(content)
    except (TypeError, ValueError) as exc:
        raise EdgeJudgeProviderError(
            f"response content is not valid JSON: {type(exc).__name__}: {exc}"
        ) from exc
