"""Adaptive-card adapter for the shared structured nutrition judgment boundary."""

from __future__ import annotations

import json
from typing import cast

from .korean_humanizer import AdaptiveGroundingInput
from .nutrition_coaching_judgment import JudgmentOption, NutritionJudgmentGrounding
from .nutrition_coaching_proposal import (
    NutritionTargets,
    coach_v2_instruction_constraints,
    coach_v2_response_schema,
)


_FACT_LABELS = {
    "evaluation_day": "평가일",
    "goal_mode": "목표 모드",
    "goal_range": "목표 범위",
    "decision": "코드 권고",
    "reason_category_ids": "판단 근거 분류",
    "target_macros": "권고 영양 범위",
    "carb_category_targets": "탄수화물 분류 범위",
}
_DECISION_ACTIONS = {
    "observe": "현재 계획을 유지하고 다음 체크인을 확인합니다.",
    "maintain": "현재 계획을 유지합니다.",
    "adjust": "코드가 제시한 안전 범위 안에서 계획을 조정합니다.",
    "safety_hold": "계획 변경을 중단하고 운영자 확인을 우선합니다.",
}


def build_adaptive_judgment_request(
    grounding_input: AdaptiveGroundingInput,
    canonical_text: str,
) -> tuple[str, str, NutritionJudgmentGrounding] | None:
    facts = dict(grounding_input.facts)
    observations = tuple(
        JudgmentOption(f"adaptive.{key}", f"{label} {facts[key]}로")
        for key, label in _FACT_LABELS.items()
        if facts.get(key)
    )
    evidence = tuple(
        JudgmentOption(item, f"검증된 적응형 근거 {item}")
        for item in grounding_input.source_cluster_ids
    )
    decision_id = {
        "calorie_adjustment_candidate": "adjust",
        "macro_redistribution_candidate": "adjust",
        "human_review": "safety_hold",
    }.get(
        grounding_input.decision_id,
        grounding_input.decision_id or facts.get("decision", ""),
    )
    decision_text = _DECISION_ACTIONS.get(decision_id)
    if not observations or not evidence or decision_text is None:
        return None
    actions = (
        JudgmentOption(decision_id, decision_text),
        JudgmentOption(
            "observe_next_checkin",
            "계획을 바꾸지 않고 다음 체크인의 변화를 확인합니다.",
        ),
    )
    limitations = (
        JudgmentOption(
            "single_day",
            "단일 체크인만으로 추세를 단정하지 않습니다.",
        ),
    )
    grounding = NutritionJudgmentGrounding(
        grounding_input.customer_key or "",
        grounding_input.revision_binding_digest,
        observations,
        evidence,
        actions,
        limitations,
        canonical_text,
    )
    current_targets = _adaptive_targets(facts.get("current_targets"))
    if current_targets is None:
        return None
    coach_observations = tuple(
        option
        for option in observations
        if option.option_id
        not in {
            "adaptive.decision",
            "adaptive.target_macros",
            "adaptive.carb_category_targets",
        }
    )
    grounding = NutritionJudgmentGrounding(
        grounding.customer_key,
        grounding.revision_binding_digest,
        coach_observations,
        grounding.evidence,
        grounding.actions,
        grounding.limitations,
        grounding.untrusted_context,
        current_targets,
        0,
        None,
        None,
        decision_id,
        facts.get("safety_held") is True,
    )
    constraints = coach_v2_instruction_constraints(
        evidence_ids=(item.option_id for item in evidence),
        focus_ids=(item.option_id for item in coach_observations),
    )
    request = {
        "schema_version": "nutrition-coach-request-v2",
        "customer_key": grounding.customer_key,
        "revision_binding_digest": grounding.revision_binding_digest,
        "input_trust": "untrusted_customer_data",
        "current_targets": current_targets.as_dict(),
        "current_checkin": {
            key: value
            for key, value in facts.items()
            if key
            not in {
                "current_targets",
                "decision",
                "target_macros",
                "carb_category_targets",
            }
        },
        "recent_history": {},
        "observations": _options(coach_observations),
        "evidence": _options(evidence),
        "approved_principles": [],
        "data_quality": {"valid_sample_count": 0},
        "untrusted_context": {"source": "adaptive_operator"},
        "response_schema": coach_v2_response_schema(
            evidence_ids=(item.option_id for item in evidence),
            focus_ids=(item.option_id for item in coach_observations),
        ),
    }
    system_prompt = (
        "You are the Coach stage for adaptive nutrition review. Interpret only "
        "the supplied customer-scoped facts and current targets, freely recommend "
        "grounded calorie and macronutrient targets, explain the decision, and "
        "draft natural Korean customer copy. Return exactly one JSON object "
        f"matching response_schema. {constraints}"
    )
    return (
        system_prompt,
        json.dumps(request, ensure_ascii=False, sort_keys=True, separators=(",", ":")),
        grounding,
    )


def replace_adaptive_judgment(canonical_text: str, judgment_text: str) -> str:
    lines = canonical_text.splitlines()
    try:
        start = lines.index("검토 필요")
        end = lines.index("고객에게는 아직 전달되지 않았습니다.")
    except ValueError:
        return canonical_text
    if start >= end:
        return canonical_text
    return "\n".join((*lines[: start + 1], judgment_text, *lines[end:]))


def _adaptive_targets(value: str | None) -> NutritionTargets | None:
    if type(value) is not str:
        return None
    try:
        raw = json.loads(value)
        values = dict(raw) if isinstance(raw, list) else raw
    except (TypeError, ValueError, json.JSONDecodeError):
        return None
    if not isinstance(values, dict):
        return None
    items = (
        values.get("calories_kcal", values.get("calories")),
        values.get("protein_g"),
        values.get("carbohydrate_g", values.get("carbs_g")),
        values.get("fat_g"),
    )
    if not all(type(item) is int for item in items):
        return None
    return NutritionTargets(*cast(tuple[int, int, int, int], items))


def _options(values: tuple[JudgmentOption, ...]) -> list[dict[str, str]]:
    return [{"id": item.option_id, "text": item.text} for item in values]
