"""Advisory LLM reconciliation for collected nutrition onboarding answers."""

from __future__ import annotations

from dataclasses import asdict, dataclass
import json
from typing import cast

from checkin_cli.nutrition_onboarding_contract import QUESTION_FIELDS


class ReconciliationError(ValueError):
    """The untrusted reconciliation response violated its contract."""


class ReconciliationUnavailable(RuntimeError):
    """The configured advisory model could not produce a safe result."""


@dataclass(frozen=True)
class NutritionOnboardingClarification:
    """One advisory request to re-answer a canonical onboarding field."""

    field: str
    kind: str
    question_ko: str


@dataclass(frozen=True)
class NutritionOnboardingReconciliation:
    """Structured advisory output that cannot authorize onboarding."""

    summary_ko: str
    facts_ko: tuple[str, ...]
    ambiguities_ko: tuple[str, ...]
    contradictions_ko: tuple[str, ...]
    safety_observations_ko: tuple[str, ...]
    clarifications: tuple[NutritionOnboardingClarification, ...] = ()

    @property
    def requires_correction(self) -> bool:
        return bool(self.clarifications)


def reconciliation_payload(
    reconciliation: NutritionOnboardingReconciliation,
) -> dict[str, object]:
    return cast(dict[str, object], asdict(reconciliation))


def reconciliation_from_payload(
    payload: object,
) -> NutritionOnboardingReconciliation:
    return parse_reconciliation_json(
        json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
    )


def render_authoritative_customer_summary(
    answers: dict[str, object],
) -> str:
    def value(field: str, *, default: str = "미입력") -> str:
        raw = answers.get(field)
        if raw is None or raw == "" or raw == []:
            return default
        if isinstance(raw, list):
            return ", ".join(str(item) for item in raw) or default
        return str(raw)

    def enum_value(field: str, labels: dict[str, str]) -> str:
        raw = answers.get(field)
        return labels.get(raw, "미입력") if isinstance(raw, str) else "미입력"

    def structured_items(field: str) -> str:
        raw = answers.get(field)
        if not isinstance(raw, dict):
            return "미입력"
        structured = cast(dict[str, object], raw)
        status = structured.get("status")
        if status == "none":
            return "없음"
        items = structured.get("items")
        if status != "provided" or not isinstance(items, list):
            return "미입력"
        provided = [
            item
            for item in cast(list[object], items)
            if isinstance(item, str) and item
        ]
        return ", ".join(provided) or "미입력"

    def has_provided_items(field: str) -> bool:
        raw = answers.get(field)
        return (
            isinstance(raw, dict)
            and cast(dict[str, object], raw).get("status") == "provided"
        )

    health_review = (
        "검토할 입력이 있습니다."
        if (
            has_provided_items("conditions")
            or has_provided_items("medications")
            or answers.get("pregnancy_breastfeeding") is not False
            or answers.get("eating_disorder_risk") is not False
        )
        else "별도 검토 입력이 없습니다."
    )
    restrictions = " · ".join(
        f"{label} {structured_items(field)}"
        for label, field in (
            ("알레르기", "allergies"),
            ("불내증", "intolerances"),
            ("종교·윤리 제한", "religious_ethical_exclusions"),
            ("비선호 식품", "disliked_foods"),
            ("식품 선호", "dietary_preferences"),
        )
    )
    goal = enum_value(
        "goal_type",
        {"loss": "감량", "maintain": "유지", "gain": "증량"},
    )
    activity = enum_value(
        "activity_category",
        {
            "sedentary": "좌식",
            "light": "가벼움",
            "moderate": "보통",
            "very_active": "매우 활동적",
            "extra_active": "최고 활동",
        },
    )
    return "\n".join(
        (
            f"- 목표: {goal}",
            f"- 활동: {activity} · {value('activity_rationale')}",
            f"- 식사: 하루 {value('meal_count')}끼",
            (
                "- 조리·예산: "
                f"{value('cooking_access')} · {value('budget_band')}"
            ),
            f"- 식사 시간: {value('schedule_constraints')}",
            f"- 알레르기·불내증·식품 제한: {restrictions}",
            f"- 건강 관련 입력: {health_review}",
        )
    )


def render_reconciliation_text(
    reconciliation: NutritionOnboardingReconciliation,
) -> str:
    if reconciliation.clarifications:
        lines = [
            "입력 내용을 정확히 반영하려고 추가 확인이 필요합니다.",
            "",
        ]
        lines.extend(
            f"- {item.question_ko}"
            for item in reconciliation.clarifications
        )
        lines.extend(
            (
                "",
                "질문에 답하면 입력 내용을 다시 정리해 드립니다.",
                "이 단계에서는 확인·승인·활성화가 진행되지 않습니다.",
            )
        )
        return "\n".join(lines)
    lines = [
        "입력 내용을 정리했어요",
        "",
        reconciliation.summary_ko,
    ]
    sections = (
        ("확인된 내용", reconciliation.facts_ko),
        ("확인이 필요한 모호함", reconciliation.ambiguities_ko),
        ("서로 맞지 않는 내용", reconciliation.contradictions_ko),
        ("안전 관련 참고사항", reconciliation.safety_observations_ko),
    )
    for title, items in sections:
        if items:
            lines.extend(("", title, *(f"- {item}" for item in items)))
    lines.extend(
        (
            "",
            "이 요약은 참고용이며 안전 판정·승인·활성화를 대신하지 않습니다.",
            "내용이 맞으면 아래 확인 버튼을 눌러 주세요.",
        )
    )
    return "\n".join(lines)


_FIELDS = {
    "summary_ko",
    "facts_ko",
    "ambiguities_ko",
    "contradictions_ko",
    "safety_observations_ko",
    "clarifications",
}
_QUESTION_FIELDS = frozenset(QUESTION_FIELDS)
_CLARIFICATION_KINDS = frozenset({"ambiguity", "contradiction"})


def _text(value: object, *, field: str, limit: int) -> str:
    if not isinstance(value, str) or not value.strip():
        raise ReconciliationError(f"{field} must be non-empty text")
    normalized = value.strip()
    if len(normalized) > limit:
        raise ReconciliationError(f"{field} exceeds its size limit")
    return normalized


def _text_list(value: object, *, field: str) -> tuple[str, ...]:
    if not isinstance(value, list) or len(value) > 30:
        raise ReconciliationError(f"{field} must be a bounded list")
    return tuple(
        _text(item, field=field, limit=300)
        for item in value
    )


def _summary(value: object) -> str:
    if isinstance(value, str):
        return _text(value, field="summary_ko", limit=1200)
    lines = _text_list(value, field="summary_ko")
    if not lines:
        raise ReconciliationError("summary_ko must not be empty")
    return _text(
        "\n".join(lines),
        field="summary_ko",
        limit=1200,
    )


def _clarifications(
    value: object,
) -> tuple[NutritionOnboardingClarification, ...]:
    if not isinstance(value, list) or len(value) > len(_QUESTION_FIELDS):
        raise ReconciliationError("clarifications must be a bounded list")
    items: list[NutritionOnboardingClarification] = []
    seen_fields: set[str] = set()
    for item in value:
        if not isinstance(item, dict) or set(item) != {
            "field",
            "kind",
            "question_ko",
        }:
            raise ReconciliationError(
                "clarification schema is not exact"
            )
        mapping = cast(dict[str, object], item)
        field = mapping["field"]
        kind = mapping["kind"]
        if not isinstance(field, str) or field not in _QUESTION_FIELDS:
            raise ReconciliationError(
                "clarification field is not canonical"
            )
        if field in seen_fields:
            raise ReconciliationError(
                "clarification fields must be unique"
            )
        if not isinstance(kind, str) or kind not in _CLARIFICATION_KINDS:
            raise ReconciliationError(
                "clarification kind is invalid"
            )
        items.append(
            NutritionOnboardingClarification(
                field=field,
                kind=kind,
                question_ko=_text(
                    mapping["question_ko"],
                    field="clarification question_ko",
                    limit=180,
                ),
            )
        )
        seen_fields.add(field)
    return tuple(items)


def parse_reconciliation_json(
    raw: str,
) -> NutritionOnboardingReconciliation:
    try:
        payload = json.loads(raw)
    except (TypeError, json.JSONDecodeError) as exc:
        raise ReconciliationError("response must be plain JSON") from exc
    if not isinstance(payload, dict) or set(payload) != _FIELDS:
        raise ReconciliationError("response schema is not exact")
    reconciliation = NutritionOnboardingReconciliation(
        summary_ko=_summary(payload["summary_ko"]),
        facts_ko=_text_list(payload["facts_ko"], field="facts_ko"),
        ambiguities_ko=_text_list(
            payload["ambiguities_ko"],
            field="ambiguities_ko",
        ),
        contradictions_ko=_text_list(
            payload["contradictions_ko"],
            field="contradictions_ko",
        ),
        safety_observations_ko=_text_list(
            payload["safety_observations_ko"],
            field="safety_observations_ko",
        ),
        clarifications=_clarifications(payload["clarifications"]),
    )
    if (
        reconciliation.ambiguities_ko
        or reconciliation.contradictions_ko
    ) and not reconciliation.clarifications:
        raise ReconciliationError(
            "ambiguous or contradictory output needs clarification"
        )
    return reconciliation


from gateway.platforms.nutrition_onboarding_reconciler import (
    NutritionOnboardingReconciler,
)
