"""Customer-first Korean copy and grouped parsing for questionnaire v2."""

from __future__ import annotations

import re
from typing import Final

from pydantic import JsonValue


CUSTOMER_V2_PROMPTS: Final = {
    "goal_type": (
        "가장 원하는 변화를 골라 주세요.\n"
        "1 체중 감량 · 2 현재 체중 유지 · 3 체중 증량"
    ),
    "target_weight_kg": (
        "대략적인 목표 체중을 알려 주세요.\n"
        "정확하지 않아도 괜찮아요. kg 숫자로 입력해 주세요. 예: 65"
    ),
    "target_date": (
        "목표를 언제쯤 이루고 싶은지 알려 주세요.\n"
        "정확하지 않아도 괜찮아요. 예: 2026-12-31"
    ),
    "meal_count": (
        "식사와 간식을 합쳐 하루에 보통 몇 번 드시나요?\n"
        "1~8 사이 숫자로 입력해 주세요. 예: 3"
    ),
    "activity_profile": (
        "평소 활동과 가장 가까운 번호를 골라 주세요.\n"
        "1 거의 앉아서 생활 · 2 가벼운 활동 · 3 주 3~5회 운동\n"
        "4 거의 매일 운동 · 5 하루 2회 운동 또는 육체노동\n"
        "가능하면 쉼표 뒤에 운동이나 걸음 수도 적어 주세요.\n"
        "예: 3, 주 3회 헬스하고 하루 8천 보"
    ),
    "food_preferences_profile": (
        "평소 식사에서 피하거나 선호하는 것을 한 번에 알려 주세요.\n"
        "1 종교나 신념 때문에 먹지 않는 음식 · 2 싫어하는 음식 · "
        "3 선호하는 음식이나 식사 방식\n"
        "알레르기나 속이 불편한 음식은 다음 질문에서 따로 확인할게요.\n"
        "각 항목이 없으면 '없음'이라고 적어 주세요.\n"
        "예: 1 종교상 돼지고기 제외 / 2 고수 / 3 한식"
    ),
    "lifestyle_profile": (
        "실제로 지킬 수 있는 식사를 만들기 위해 생활 조건을 알려 주세요.\n"
        "1 이용 가능한 조리도구 · 2 한 끼 예산 · 3 식사 시간 제약\n"
        "시간 제약이 없으면 3번에 '없음'이라고 적어 주세요.\n"
        "예: 1 전자레인지 / 2 한 끼 1만 원 / 3 평일 점심 외식"
    ),
    "allergies": (
        "음식 알레르기가 있나요?\n"
        "안전한 식사 제안을 위해 꼭 확인해요. 예: 땅콩, 새우 · 없으면 '없음'"
    ),
    "intolerances": (
        "먹었을 때 속이 불편해지는 음식이 있나요?\n"
        "예: 우유, 밀가루 · 없으면 '없음'"
    ),
    "date_of_birth": (
        "현재 만 나이를 숫자로 알려 주세요.\n"
        "칼로리 계산과 성인 여부 확인에만 사용해요. 예: 34"
    ),
    "equation_sex_basis": (
        "칼로리 계산에 사용할 기준을 골라 주세요.\n"
        "칼로리 계산에 반드시 필요한 기준이에요. 다른 용도로 판단하지 않아요.\n"
        "1 남성 기준 · 2 여성 기준"
    ),
    "height_cm": (
        "키를 cm 숫자로 알려 주세요.\n"
        "칼로리 계산에 사용해요. 예: 170"
    ),
    "weight_kg": (
        "현재 체중을 kg 숫자로 알려 주세요.\n"
        "칼로리와 목표 속도를 계산하는 데 사용해요. 예: 70.2"
    ),
    "conditions": (
        "현재 진단받았거나 치료 중인 건강 문제가 있나요?\n"
        "식사 제안의 안전을 확인하기 위한 질문이에요. 없으면 '없음'\n"
        "진단이나 치료를 위한 질문은 아니에요."
    ),
    "medications": (
        "현재 복용하는 약이나 보충제가 있나요?\n"
        "식사 제안의 안전을 확인하기 위해 이름만 받아요. 없으면 '없음'\n"
        "복용 방법을 안내하기 위한 질문은 아니에요."
    ),
    "pregnancy_breastfeeding": (
        "현재 임신 중이거나 수유 중인가요?\n"
        "필요한 안전 검토가 있는지 확인하기 위한 질문이에요.\n"
        "1 예 · 2 아니오 · 3 확실하지 않음\n"
        "진단을 위한 질문은 아니에요."
    ),
    "eating_disorder_risk": (
        "식사나 체중 문제로 진단·치료를 받았거나 현재 걱정되는 점이 있나요?\n"
        "필요한 안전 안내가 있는지 확인해요. 자세히 적지 않아도 돼요.\n"
        "1 예 · 2 아니오 · 3 확실하지 않음\n"
        "진단을 위한 질문은 아니에요."
    ),
}
CUSTOMER_V2_CLARIFICATIONS: Final = {
    "date_of_birth": "현재 만 나이를 18~120 사이 숫자로 입력해 주세요. 예: 34",
    "equation_sex_basis": "계산 기준을 골라 주세요.\n1 남성 기준 · 2 여성 기준",
    "height_cm": "키를 cm 숫자 하나로 입력해 주세요. 예: 170",
    "weight_kg": "현재 체중을 kg 숫자 하나로 입력해 주세요. 예: 70.2",
    "target_weight_kg": "목표 체중을 kg 숫자 하나로 입력해 주세요. 예: 65",
    "target_date": "목표 날짜를 YYYY-MM-DD로 입력해 주세요. 예: 2026-12-31",
    "meal_count": "하루 식사와 간식 횟수를 1~8 사이 숫자로 입력해 주세요.",
}
_NONE_ANSWERS: Final = frozenset({
    "none",
    "없음",
    "없어요",
    "없습니다",
    "해당 없음",
    "해당없음",
    "특별히 없음",
    "특별히 없어요",
})
_UNKNOWN_ANSWERS: Final = frozenset({
    "모름",
    "모르겠음",
    "모르겠어요",
    "잘 모름",
    "잘 모르겠음",
    "잘 모르겠어요",
    "기억 안 남",
    "기억이 안 남",
    "기억이 안 나요",
    "확인 필요",
})
_UNKNOWN_STRUCTURED_FIELDS: Final = frozenset({
    "allergies",
    "intolerances",
    "religious_ethical_exclusions",
    "disliked_foods",
    "dietary_preferences",
    "conditions",
    "medications",
})
_UNKNOWN_BOOLEAN_FIELDS: Final = frozenset({
    "pregnancy_breastfeeding",
    "eating_disorder_risk",
})
_ACTIVITY: Final = {
    "1": "sedentary",
    "2": "light",
    "3": "moderate",
    "4": "very_active",
    "5": "extra_active",
}


class CustomerV2AnswerError(ValueError):
    """Raised when one customer-v2 answer cannot be parsed safely."""


class CustomerV2RequiredAnswerUnknown(CustomerV2AnswerError):
    """Raised when a required answer needs human assistance."""


def is_customer_v2_unknown(value: str) -> bool:
    """Recognize explicit uncertainty without treating it as absence."""
    normalized = " ".join(value.split()).casefold().strip(" .,!?:;~…。！？")
    return normalized in _UNKNOWN_ANSWERS


def customer_v2_unknown_value(field: str) -> JsonValue:
    """Return a typed unknown value or require an operator handoff."""
    if field == "food_preferences_profile":
        return {
            canonical: {"status": "unknown", "items": []}
            for canonical in (
                "religious_ethical_exclusions",
                "disliked_foods",
                "dietary_preferences",
            )
        }
    if field in _UNKNOWN_STRUCTURED_FIELDS:
        return {"status": "unknown", "items": []}
    if field in _UNKNOWN_BOOLEAN_FIELDS:
        return None
    raise CustomerV2RequiredAnswerUnknown(
        f"{field} required answer needs operator help"
    )


def customer_v2_unknown_help(field: str) -> str:
    """Explain a required unknown answer without blaming the customer."""
    label = {
        "goal_type": "원하는 변화",
        "target_weight_kg": "목표 체중",
        "target_date": "목표 시점",
        "meal_count": "하루 식사 횟수",
        "activity_profile": "평소 활동",
        "lifestyle_profile": "조리 환경과 예산",
        "date_of_birth": "만 나이",
        "equation_sex_basis": "칼로리 계산 기준",
        "height_cm": "키",
        "weight_kg": "현재 체중",
    }.get(field, "현재 답변")
    return (
        f"{label}은 다음 단계를 위해 확인이 필요해요. "
        "임의로 추정하지 않고 담당자에게 도움을 요청했습니다.\n"
        "확인한 뒤 같은 질문에 다시 답하면 이어서 진행됩니다."
    )


def customer_v2_clarification(field: str, fallback: str) -> str:
    """Return parser-aligned clarification copy for a canonical field."""
    return CUSTOMER_V2_CLARIFICATIONS.get(
        field,
        CUSTOMER_V2_PROMPTS.get(field, fallback),
    )


def _numbered_sections(value: str) -> tuple[str, str, str]:
    parts = re.split(r"\s*(?:/|;|\n)\s*", value.strip())
    sections: dict[int, str] = {}
    for part in parts:
        match = re.fullmatch(r"([1-3])\s*[).:]?\s*(.+)", part)
        if match is None:
            raise CustomerV2AnswerError("invalid grouped answer")
        index = int(match.group(1))
        if index in sections:
            raise CustomerV2AnswerError("duplicate grouped answer")
        sections[index] = match.group(2).strip()
    if set(sections) != {1, 2, 3}:
        raise CustomerV2AnswerError("incomplete grouped answer")
    return sections[1], sections[2], sections[3]


def _structured_answer(value: str) -> dict[str, JsonValue]:
    if value.casefold() in _NONE_ANSWERS:
        return {"status": "none", "items": []}
    return {
        "status": "provided",
        "items": [item.strip() for item in value.split(",")],
    }


def parse_customer_v2_answer(
    field: str,
    value: str,
) -> tuple[bool, JsonValue]:
    """Parse a v2-only screen or defer a canonical field to the legacy parser."""
    if is_customer_v2_unknown(value):
        return True, customer_v2_unknown_value(field)
    if field == "date_of_birth":
        match = re.fullmatch(r"(?:만\s*)?(\d{1,3})\s*(?:세)?", value)
        if match is None:
            raise CustomerV2AnswerError("invalid adult age")
        age = int(match.group(1))
        if not 18 <= age <= 120:
            raise CustomerV2AnswerError("invalid adult age")
        return True, age
    if field == "activity_profile":
        match = re.fullmatch(r"([1-5])(?:\s*[,./-]\s*(.+))?", value)
        if match is None:
            raise CustomerV2AnswerError("invalid activity profile")
        return True, {
            "activity_category": _ACTIVITY[match.group(1)],
            "activity_rationale": (match.group(2) or "").strip(),
        }
    if field == "food_preferences_profile":
        exclusions, dislikes, preferences = _numbered_sections(value)
        return True, {
            "religious_ethical_exclusions": _structured_answer(exclusions),
            "disliked_foods": _structured_answer(dislikes),
            "dietary_preferences": _structured_answer(preferences),
        }
    if field == "lifestyle_profile":
        cooking, budget, schedule = _numbered_sections(value)
        if cooking.casefold() in _NONE_ANSWERS or budget.casefold() in _NONE_ANSWERS:
            raise CustomerV2AnswerError("cooking and budget are required")
        return True, {
            "cooking_access": cooking,
            "budget_band": budget,
            "schedule_constraints": schedule,
        }
    if field == "equation_sex_basis" and value == "3":
        raise CustomerV2AnswerError("calculation basis is required")
    return False, value
