"""Korean prompt copy and boundary parsing for nutrition onboarding."""

from __future__ import annotations

import re
from datetime import date
from typing import Any

from gateway.platforms.telegram_nutrition_onboarding_customer_v2 import (
    CUSTOMER_V2_PROMPTS,
    parse_customer_v2_answer,
)

_MEASUREMENT_UNITS = {
    "height_cm": "cm",
    "weight_kg": "kg",
    "target_weight_kg": "kg",
}
_ACTIVITY_CATEGORIES = {
    "좌식": "sedentary",
    "가벼움": "light",
    "보통": "moderate",
    "매우 활동적": "very_active",
    "최고 활동": "extra_active",
}
_CLOSED_CHOICES = {
    "activity_category": _ACTIVITY_CATEGORIES,
    "equation_sex_basis": {
        "남성": "male",
        "여성": "female",
        "선택 안 함": "decline",
    },
    "goal_type": {
        "감량": "loss",
        "유지": "maintain",
        "증량": "gain",
    },
}
_UNAMBIGUOUS_CHOICE_QUALIFIERS = ("", " 수준")
_UNAMBIGUOUS_CHOICE_COPULAS = ("", "입니다", "이에요", "예요")
_KOREAN_MEAL_COUNTS = {
    "한": 1,
    "두": 2,
    "세": 3,
    "네": 4,
    "다섯": 5,
    "여섯": 6,
    "일곱": 7,
    "여덟": 8,
    "아홉": 9,
    "열": 10,
}
_STRUCTURED_NONE_ANSWERS = {
    "allergies": {"음식 알레르기는 없습니다."},
    "intolerances": {"음식 불내증은 없습니다."},
    "religious_ethical_exclusions": {
        "종교적 또는 윤리적으로 제외하는 음식은 없습니다."
    },
    "disliked_foods": {"싫어해서 피하는 음식은 없습니다."},
    "dietary_preferences": {"특별한 식단 선호는 없습니다."},
    "conditions": {"진단받은 질환은 없습니다."},
    "medications": {"복용 중인 약이나 보충제는 없습니다."},
}
_BOOLEAN_ANSWERS = {
    "예": True,
    "아니오": False,
    "확실하지 않음": None,
    "임신 또는 수유에 해당하지 않습니다.": False,
    "섭식장애 위험이나 과거력은 없습니다.": False,
    "1": True,
    "2": False,
    "3": None,
}
_NONE_ANSWERS = {
    "none",
    "없음",
    "없어요",
    "없습니다",
    "해당 없음",
    "해당없음",
    "특별히 없음",
    "특별히 없어요",
    "아직 없음",
    "아직 없어요",
}
_CHOICE_ALIASES = {
    "equation_sex_basis": {
        "1": "male",
        "남자": "male",
        "2": "female",
        "여자": "female",
        "3": "decline",
        "선택하지 않음": "decline",
    },
    "activity_category": {
        "1": "sedentary",
        "2": "light",
        "3": "moderate",
        "4": "very_active",
        "5": "extra_active",
    },
    "goal_type": {
        "1": "loss",
        "살 빼기": "loss",
        "체중 감량": "loss",
        "2": "maintain",
        "체중 유지": "maintain",
        "3": "gain",
        "살 찌우기": "gain",
        "체중 증량": "gain",
    },
}


def _parse_measurement(value: str, unit: str) -> str | None:
    if " 또는 " in value or " 혹은 " in value:
        return None
    matches = re.findall(rf"(?P<number>\d+(?:\.\d+)?)\s*{unit}", value.casefold())
    if len(matches) != 1:
        return None
    number = matches[0]
    return number.rstrip("0").rstrip(".") if "." in number else number


def _parse_meal_count(value: str) -> int:
    normalized = re.sub(r"^하루\s*", "", value.strip())
    numeric = re.fullmatch(r"(?P<count>\d+)\s*(?:끼|회)", normalized)
    if numeric:
        return int(numeric.group("count"))
    korean = re.fullmatch(
        rf"(?P<count>{'|'.join(_KOREAN_MEAL_COUNTS)})\s*(?:끼|회)",
        normalized,
    )
    if korean:
        return _KOREAN_MEAL_COUNTS[korean.group("count")]
    return int(normalized)


def _parse_date(value: str) -> str | None:
    korean_dates = re.findall(r"(\d{4})년\s*(\d{1,2})월\s*(\d{1,2})일", value)
    if len(korean_dates) == 1:
        year, month, day = (int(part) for part in korean_dates[0])
    elif len(korean_dates) > 1:
        return None
    else:
        numeric_date = re.fullmatch(
            r"(\d{4})\s*[./-]\s*(\d{1,2})\s*[./-]\s*(\d{1,2})\.?",
            value,
        )
        if numeric_date is None:
            return None
        year, month, day = (int(part) for part in numeric_date.groups())
    try:
        return date(year, month, day).isoformat()
    except ValueError:
        return None


PROMPTS = {
    "date_of_birth": "생년월일을 입력해 주세요.\n예: 1990-03-02 또는 1990. 3. 2.",
    "equation_sex_basis": (
        "칼로리 계산에 사용할 기준을 번호로 입력해 주세요.\n"
        "1 남성 · 2 여성 · 3 선택 안 함"
    ),
    "height_cm": "키를 숫자로 입력해 주세요.\n예: 170",
    "weight_kg": "현재 체중을 숫자로 입력해 주세요.\n예: 70.2",
    "activity_category": (
        "평소 활동량과 가장 가까운 번호를 입력해 주세요.\n"
        "1 거의 앉아서 생활 · 2 가벼운 활동 · 3 주 3~5회 운동\n"
        "4 거의 매일 운동 · 5 하루 2회 운동 또는 육체노동"
    ),
    "activity_rationale": (
        "평소 운동 횟수와 생활 활동을 짧게 적어 주세요.\n"
        "예: 주 3회 헬스, 출퇴근 때 하루 8천 보"
    ),
    "goal_type": "가장 중요한 목표 번호를 입력해 주세요.\n1 감량 · 2 유지 · 3 증량",
    "target_weight_kg": "목표 체중을 입력해 주세요.\n예: 65.5 · 정하지 않았다면 '없음'",
    "target_date": "목표 날짜를 입력해 주세요.\n예: 2026-12-31 · 정하지 않았다면 '없음'",
    "allergies": "음식 알레르기가 있나요?\n예: 땅콩, 새우 · 없으면 '없음'",
    "intolerances": "먹으면 불편한 음식이 있나요?\n예: 우유, 밀가루 · 없으면 '없음'",
    "religious_ethical_exclusions": "종교·윤리상 먹지 않는 음식이 있나요?\n없으면 '없음'",
    "disliked_foods": "식단에서 빼고 싶은 음식이 있나요?\n없으면 '없음'",
    "dietary_preferences": "선호하는 식사 방식이나 음식이 있나요?\n예: 한식 위주 · 없으면 '없음'",
    "conditions": "진단받았거나 치료 중인 건강 문제가 있나요?\n없으면 '없음'",
    "medications": "현재 복용하는 약이나 보충제가 있나요?\n없으면 '없음'",
    "pregnancy_breastfeeding": "현재 임신·수유 중인가요?\n1 예 · 2 아니오 · 3 잘 모르겠음",
    "eating_disorder_risk": "섭식장애 진단·치료 경험 또는 현재 우려가 있나요?\n1 예 · 2 아니오 · 3 잘 모르겠음",
    "cooking_access": "평소 이용 가능한 조리 환경을 적어 주세요.\n예: 집에서 가스레인지와 전자레인지 사용",
    "budget_band": "하루 식비 예산을 대략 적어 주세요.\n예: 하루 2만원",
    "meal_count": "보통 하루 몇 끼 드시나요?\n2~6 사이 숫자로 입력해 주세요. 예: 3",
    "schedule_constraints": "식사하기 어려운 시간대가 있나요?\n예: 평일 점심은 외식 · 없으면 '없음'",
    **{
        f"customer_v2:{field}": prompt
        for field, prompt in CUSTOMER_V2_PROMPTS.items()
    },
}


def card(status: Any) -> tuple[str, str | None, str]:
    if status.state.value == "collecting":
        version = getattr(status, "questionnaire_version", None)
        key = (
            f"{version}:{status.next_field}"
            if isinstance(version, str)
            else status.next_field
        )
        return PROMPTS[key], None, "customer"
    if status.state.value == "customer_attestation":
        return "입력 요약이 준비됐습니다. 본인이 입력한 내용임을 확인해 주세요.", "attest", "customer"
    if status.state.value == "input_help":
        return (
            "고객이 현재 질문의 답을 확인하기 어려워 도움을 요청했습니다. "
            "고객과 내용을 확인한 뒤 질문을 다시 열어 주세요.",
            None,
            "owner",
        )
    if status.state.value == "owner_review":
        return "고객 영양 기초정보의 운영자 검토가 필요합니다.", "owner_ok", "owner"
    if status.state.value == "safety_hold":
        return "외부 임상 검토가 필요해 온보딩이 보류됐습니다.", None, "owner"
    if status.state.value == "ready":
        return (
            "온보딩 자료와 준비 검증이 완료됐습니다. 운영자 활성화 전까지 고객 전달은 비활성 상태입니다.",
            None,
            "owner",
        )
    if status.state.value == "rejected":
        return (
            "온보딩 승인이 거절됐습니다. 고객 영양 코칭은 비활성 상태입니다.",
            None,
            "owner",
        )
    return "승인이 완료됐습니다. 안전 자료를 최종 생성하고 있습니다.", None, "owner"


def parse_legacy_activity_answer(
    value: object,
) -> tuple[str, str] | None:
    if not isinstance(value, str):
        return None
    for label, category in _ACTIVITY_CATEGORIES.items():
        marker = f"활동 수준: {label}. "
        if value.startswith(marker):
            rationale = value.removeprefix(marker).strip()
            if rationale:
                return category, rationale
    return None


def _parse_closed_choice(value: str, choices: dict[str, str]) -> str | None:
    normalized = value.rstrip(".。!！")
    for label, canonical in choices.items():
        if any(
            normalized == f"{label}{qualifier}{copula}"
            for qualifier in _UNAMBIGUOUS_CHOICE_QUALIFIERS
            for copula in _UNAMBIGUOUS_CHOICE_COPULAS
        ):
            return canonical
    return None


def parse_answer(
    field: str,
    text: str,
    *,
    questionnaire_version: str | None = None,
) -> object:
    value = text.strip()
    if questionnaire_version == "customer_v2":
        handled, parsed = parse_customer_v2_answer(field, value)
        if handled:
            return parsed
    if field == "date_of_birth":
        parsed_date = _parse_date(value)
        if parsed_date is None:
            raise ValueError("invalid date")
        return parsed_date
    structured_fields = {
        "allergies",
        "intolerances",
        "religious_ethical_exclusions",
        "disliked_foods",
        "dietary_preferences",
        "conditions",
        "medications",
    }
    if field in structured_fields:
        if value.casefold() in _NONE_ANSWERS or value in _STRUCTURED_NONE_ANSWERS[field]:
            return {"status": "none", "items": []}
        return {
            "status": "provided",
            "items": [item.strip() for item in value.split(",")],
        }
    closed_choices = _CLOSED_CHOICES.get(field)
    if closed_choices is not None:
        alias = _CHOICE_ALIASES.get(field, {}).get(value)
        if alias is not None:
            return alias
        exact = _parse_closed_choice(value, closed_choices)
        if exact is not None:
            return exact
    if field == "activity_category":
        exact = next(
            (
                category
                for label, category in _ACTIVITY_CATEGORIES.items()
                if value == f"{label} 활동량입니다."
            ),
            None,
        )
        if exact is not None:
            return exact
        legacy = parse_legacy_activity_answer(value)
        if legacy is not None:
            return legacy[0]
        raise ValueError("invalid activity category")
    mappings = {
        "equation_sex_basis": {
            "네, 남성입니다.": "male",
            "네, 여성입니다.": "female",
        },
        "goal_type": {
            "현재 체중을 유지하고 싶습니다.": "maintain",
        },
    }
    if field in mappings:
        mapped = mappings[field].get(value)
        if mapped is not None:
            return mapped
        if field in _CLOSED_CHOICES:
            raise ValueError("invalid closed choice")
    if field in {"pregnancy_breastfeeding", "eating_disorder_risk"}:
        if value not in _BOOLEAN_ANSWERS:
            raise ValueError("invalid boolean choice")
        return _BOOLEAN_ANSWERS[value]
    if field in {"target_weight_kg", "target_date"} and (
        value.casefold() in _NONE_ANSWERS
        or "정하지 않겠습니다" in value
    ):
        return None
    if field == "target_date":
        parsed_date = _parse_date(value)
        if parsed_date is None:
            raise ValueError("invalid date")
        return parsed_date
    unit = _MEASUREMENT_UNITS.get(field)
    if unit:
        measurement = _parse_measurement(value, unit)
        if measurement is not None:
            return measurement
    if field == "budget_band" and "보통" in value:
        return "보통"
    if field == "meal_count":
        counts = re.findall(
            rf"하루\s*(\d+|{'|'.join(_KOREAN_MEAL_COUNTS)})\s*끼",
            value,
        )
        if len(counts) == 1:
            count = counts[0]
            return int(count) if count.isdigit() else _KOREAN_MEAL_COUNTS[count]
        if len(counts) > 1:
            return value
        return _parse_meal_count(value)
    return value


def answer_error(
    field: str,
    *,
    questionnaire_version: str | None = None,
) -> str:
    key = (
        f"{questionnaire_version}:{field}"
        if isinstance(questionnaire_version, str)
        else field
    )
    prompt = PROMPTS.get(key, "현재 질문의 예시를 확인해 주세요.")
    return (
        "답을 이해하지 못했어요. 입력한 내용은 저장하지 않았습니다.\n\n"
        f"{prompt}"
    )


def answer_handoff(field: str, *, questionnaire_version: str | None) -> str:
    """Explain that repeated input trouble has reached a human helper."""
    return (
        answer_error(field, questionnaire_version=questionnaire_version)
        + "\n\n같은 답을 두 번 이해하지 못해 담당자에게 도움을 요청했습니다. "
        "확인 후 같은 질문부터 다시 이어갈 수 있어요."
    )
