"""W21B-w5 STEP5-B — light-FP marker-selection LLM provider.

Thin LLM IO boundary for the simplified floor-plan generator. The pure module
(``floor_plan_light_prompt.py``) owns the prompt bundle assembly, the schema and
the structural validator; this module only turns a built ``{system, user,
schema}`` bundle into a model response (render-role selection + the room-aware
schematic prompt). Text LLM only — the sidecar PNG is rendered by the step's own
image client, not here.

Mirrors the fail-closed discipline of ``bg_space_partition_provider`` /
``shot_projection_card_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_light_fp_llm_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_floor_plan_light_v1"
PROVIDER_MODEL_DEFAULT: str = "openai/gpt-6-astra"
MAX_COMPLETION_TOKENS_DEFAULT: int = 4000
TIMEOUT_SECONDS_DEFAULT: int = 180
RESPONSE_SCHEMA_NAME: str = "floor_plan_light_fp"


class FloorPlanLightProviderError(Exception):
    """Fail-closed signal for the W21B-w5 light-FP provider path."""


def litellm_light_fp_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]:
    """Select markers + write the schematic prompt via a single litellm call.

    ``prompt_bundle`` is exactly what ``build_light_fp_llm_prompt_bundle``
    assembled (``{system, user, schema}``). Returns the raw parsed JSON dict; the
    pure module's ``validate_light_fp_output`` gates it (structural skeleton
    integrity). The completion call count stays 0 whenever the helper
    fail-closes during preflight.
    """
    if not isinstance(prompt_bundle, dict):
        raise FloorPlanLightProviderError("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 FloorPlanLightProviderError(
            "prompt_bundle.system missing or non-string"
        )
    if not isinstance(user, str) or not user:
        raise FloorPlanLightProviderError(
            "prompt_bundle.user missing or non-string"
        )
    if not isinstance(schema, dict) or not schema:
        raise FloorPlanLightProviderError(
            "prompt_bundle.schema missing or non-dict"
        )

    from app.core.openai_keys import has_openai_key
    if not has_openai_key():
        raise FloorPlanLightProviderError(
            "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 FloorPlanLightProviderError(
            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 FloorPlanLightProviderError(
            f"litellm.completion raised: {type(exc).__name__}: {exc}"
        ) from exc

    choices = getattr(response, "choices", None) or []
    if not choices:
        raise FloorPlanLightProviderError("response.choices is empty")
    choice = choices[0]
    msg = getattr(choice, "message", None)
    if msg is None:
        raise FloorPlanLightProviderError(
            "response.choices[0].message is missing"
        )
    if getattr(msg, "refusal", None):
        raise FloorPlanLightProviderError(
            f"provider emitted a refusal: {str(getattr(msg, 'refusal'))[:200]!r}"
        )
    content = getattr(msg, "content", None)
    if not content:
        raise FloorPlanLightProviderError(
            "response.choices[0].message.content is empty"
        )
    if getattr(choice, "finish_reason", None) != "stop":
        raise FloorPlanLightProviderError(
            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 FloorPlanLightProviderError(
            f"response content is not valid JSON: {type(exc).__name__}: {exc}"
        ) from exc
