from __future__ import annotations from datetime import datetime, date from typing import List from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, InputMediaPhoto from telegram.ext import ConversationHandler, CommandHandler, MessageHandler, filters from app.core.db import db_session from app.models.candidate import Candidate from app.repositories.admin_repo import AdminRepository from app.utils.common import csv_to_list, list_to_csv, calc_age from app.services.admin_bootstrap import ensure_primary_admin ( S_NAME, S_GENDER, S_BIRTH, S_HEIGHT, S_WEIGHT, S_COUNTRY, S_CITY, S_CITIZEN, S_VISA, S_LANGS, S_EDU, S_OCCUP, S_INCOME, S_RELIGION, S_MARITAL, S_CHILDREN, S_CHILDREN_NOTES, S_SMOKING, S_ALCOHOL, S_HEALTH, S_HOBBIES_TAGS, S_HOBBIES_FREE, S_GOAL, S_PARTNER, S_AVATAR, S_GALLERY, S_CONSENTS, S_CONFIRM ) = range(28) GENDER_KB = [["жен", "муж"], ["другое"]] YESNO_KB = [["да", "нет"]] SMOKE_ALC_KB = [["нет", "иногда", "да"]] MARITAL_KB = [["никогда не был(а) в браке"], ["в разводе"], ["вдова/вдовец"]] INCOME_KB = [["до 2 млн KRW", "2–4 млн"], ["4–6 млн", "6+ млн"], ["не указывать"]] def build_conversation() -> ConversationHandler: return ConversationHandler( entry_points=[CommandHandler("start", start), CommandHandler("new", start)], states={ S_NAME: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_name)], S_GENDER: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_gender)], S_BIRTH: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_birth)], S_HEIGHT: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_height)], S_WEIGHT: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_weight)], S_COUNTRY: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_country)], S_CITY: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_city)], S_CITIZEN: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_citizen)], S_VISA: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_visa)], S_LANGS: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_langs)], S_EDU: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_edu)], S_OCCUP: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_occup)], S_INCOME: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_income)], S_RELIGION: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_religion)], S_MARITAL: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_marital)], S_CHILDREN: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_children)], S_CHILDREN_NOTES: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_children_notes)], S_SMOKING: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_smoking)], S_ALCOHOL: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_alcohol)], S_HEALTH: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_health)], S_HOBBIES_TAGS: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_hobbies_tags)], S_HOBBIES_FREE: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_hobbies_free)], S_GOAL: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_goal)], S_PARTNER: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_partner)], S_AVATAR: [ MessageHandler(filters.PHOTO, s_avatar), MessageHandler(filters.TEXT & ~filters.COMMAND, s_avatar), ], S_GALLERY: [ MessageHandler(filters.PHOTO, s_gallery), MessageHandler(filters.TEXT & ~filters.COMMAND, s_gallery), ], S_CONSENTS: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_consents)], S_CONFIRM: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_confirm)], }, fallbacks=[CommandHandler("cancel", cancel)], allow_reentry=True, ) async def start(update, context): with db_session() as s: ensure_primary_admin(s) exists = s.query(Candidate).filter(Candidate.telegram_id == update.effective_user.id).first() if exists: await update.message.reply_text( "Анкета уже существует. Посмотреть — /my, редактирование — /edit (скоро), начать заново — /new", reply_markup=ReplyKeyboardRemove(), ) return ConversationHandler.END await update.message.reply_text( "Привет! Это анкета для брачного сервиса с сопровождением до визы F‑6 🇰🇷\nКак тебя зовут?", reply_markup=ReplyKeyboardRemove(), ) return S_NAME async def s_name(update, context): context.user_data["full_name"] = update.message.text.strip() await update.message.reply_text("Твой пол?", reply_markup=ReplyKeyboardMarkup(GENDER_KB, one_time_keyboard=True, resize_keyboard=True)) return S_GENDER async def s_gender(update, context): context.user_data["gender"] = update.message.text.strip().lower() await update.message.reply_text("Дата рождения (ДД.ММ.ГГГГ):", reply_markup=ReplyKeyboardRemove()) return S_BIRTH async def s_birth(update, context): try: d = datetime.strptime(update.message.text.strip(), "%d.%m.%Y").date() except Exception: await update.message.reply_text("Неверный формат. Пример: 31.12.1995") return S_BIRTH context.user_data["birth_date"] = d await update.message.reply_text("Рост (см):") return S_HEIGHT async def s_height(update, context): try: context.user_data["height_cm"] = float(update.message.text.replace(",", ".")) except Exception: await update.message.reply_text("Введи число, например 168") return S_HEIGHT await update.message.reply_text("Вес (кг):") return S_WEIGHT async def s_weight(update, context): try: context.user_data["weight_kg"] = float(update.message.text.replace(",", ".")) except Exception: await update.message.reply_text("Введи число, например 55") return S_WEIGHT await update.message.reply_text("Страна проживания:") return S_COUNTRY async def s_country(update, context): context.user_data["country"] = update.message.text.strip() await update.message.reply_text("Город:") return S_CITY async def s_city(update, context): context.user_data["city"] = update.message.text.strip() await update.message.reply_text("Гражданство (РФ/Казахстан/Узбекистан и т.п.):") return S_CITIZEN async def s_citizen(update, context): context.user_data["citizenship"] = update.message.text.strip() await update.message.reply_text("Текущий визовый статус в Корее (нет/F‑4/H‑1 и т.п.):") return S_VISA async def s_visa(update, context): context.user_data["visa_status"] = update.message.text.strip() await update.message.reply_text("Языки (через запятую, напр.: ru, ko, en):") return S_LANGS async def s_langs(update, context): context.user_data["languages"] = list_to_csv(csv_to_list(update.message.text)) await update.message.reply_text("Образование (высшее/колледж/иное):") return S_EDU async def s_edu(update, context): context.user_data["education"] = update.message.text.strip() await update.message.reply_text("Профессия:") return S_OCCUP async def s_occup(update, context): context.user_data["occupation"] = update.message.text.strip() await update.message.reply_text( "Доход (диапазон):", reply_markup=ReplyKeyboardMarkup(INCOME_KB, one_time_keyboard=True, resize_keyboard=True), ) return S_INCOME async def s_income(update, context): context.user_data["income_range"] = update.message.text.strip() await update.message.reply_text("Религия (если нет, напиши «нет»):", reply_markup=ReplyKeyboardRemove()) return S_RELIGION async def s_religion(update, context): context.user_data["religion"] = update.message.text.strip() await update.message.reply_text( "Семейное положение:", reply_markup=ReplyKeyboardMarkup(MARITAL_KB, one_time_keyboard=True, resize_keyboard=True), ) return S_MARITAL async def s_marital(update, context): context.user_data["marital_status"] = update.message.text.strip() await update.message.reply_text("Есть дети?", reply_markup=ReplyKeyboardMarkup(YESNO_KB, one_time_keyboard=True, resize_keyboard=True)) return S_CHILDREN async def s_children(update, context): ans = update.message.text.strip().lower() context.user_data["has_children"] = (ans == "да") if ans == "да": await update.message.reply_text("Коротко про детей (кол-во/возраст/с кем проживают):", reply_markup=ReplyKeyboardRemove()) return S_CHILDREN_NOTES context.user_data["children_notes"] = None await update.message.reply_text("Курение:", reply_markup=ReplyKeyboardMarkup(SMOKE_ALC_KB, one_time_keyboard=True, resize_keyboard=True)) return S_SMOKING async def s_children_notes(update, context): context.user_data["children_notes"] = update.message.text.strip() await update.message.reply_text("Курение:", reply_markup=ReplyKeyboardMarkup(SMOKE_ALC_KB, one_time_keyboard=True, resize_keyboard=True)) return S_SMOKING async def s_smoking(update, context): context.user_data["smoking"] = update.message.text.strip().lower() await update.message.reply_text("Алкоголь:", reply_markup=ReplyKeyboardMarkup(SMOKE_ALC_KB, one_time_keyboard=True, resize_keyboard=True)) return S_ALCOHOL async def s_alcohol(update, context): context.user_data["alcohol"] = update.message.text.strip().lower() await update.message.reply_text("Здоровье/особенности (если есть; иначе «нет»):", reply_markup=ReplyKeyboardRemove()) return S_HEALTH async def s_health(update, context): context.user_data["health_notes"] = update.message.text.strip() await update.message.reply_text("Хобби/интересы (теги через запятую, напр.: пение, путешествия, k‑drama):") return S_HOBBIES_TAGS async def s_hobbies_tags(update, context): context.user_data["hobbies_tags"] = list_to_csv(csv_to_list(update.message.text)) await update.message.reply_text("Расскажи подробнее про интересы (свободный текст):") return S_HOBBIES_FREE async def s_hobbies_free(update, context): context.user_data["hobbies_free"] = update.message.text.strip() await update.message.reply_text("Твоя основная цель (например: «брак с гражданином Кореи (F‑6)»):") return S_GOAL async def s_goal(update, context): context.user_data["goal"] = update.message.text.strip() await update.message.reply_text("Пожелания к партнёру (возраст, ценности, город, религия и т.п.):") return S_PARTNER async def s_partner(update, context): context.user_data["partner_prefs"] = update.message.text.strip() await update.message.reply_text("Загрузи своё фото (аватар):") return S_AVATAR async def s_avatar(update, context): if not update.message.photo: await update.message.reply_text("Пожалуйста, пришли фотографию.") return S_AVATAR context.user_data["avatar_file_id"] = update.message.photo[-1].file_id context.user_data["gallery_file_ids"] = [] await update.message.reply_text("Можно добавить до 9 дополнительных фото. Пришли их. Когда закончишь — напиши «готово».") return S_GALLERY async def s_gallery(update, context): if update.message.photo: fid = update.message.photo[-1].file_id gallery: List[str] = context.user_data.get("gallery_file_ids", []) if len(gallery) < 9: gallery.append(fid) context.user_data["gallery_file_ids"] = gallery await update.message.reply_text(f"Добавлено фото ({len(gallery)}/9). Ещё? Или напиши «готово».") else: await update.message.reply_text("Лимит 9 фото достигнут. Напиши «готово».") return S_GALLERY if (update.message.text or "").strip().lower() != "готово": await update.message.reply_text("Если с фото закончили — напиши «готово».") return S_GALLERY await update.message.reply_text("Подтверди согласие на обработку персональных данных. Напиши: «согласен» или «согласна».") return S_CONSENTS async def s_consents(update, context): ans = (update.message.text or "").strip().lower() if ans not in ("согласен", "согласна"): await update.message.reply_text("Нужно написать «согласен» или «согласна».") return S_CONSENTS context.user_data["consent_personal"] = True context.user_data["consent_policy"] = True d = context.user_data age = calc_age(d.get("birth_date")) summary = ( f"Проверь данные:\n\n" f"Имя: {d.get('full_name')}\n" f"Пол: {d.get('gender')}\n" f"Дата рождения: {d.get('birth_date')} (возраст: {age})\n" f"Рост/вес: {d.get('height_cm')} см / {d.get('weight_kg')} кг\n" f"Страна/город: {d.get('country')}, {d.get('city')}\n" f"Гражданство: {d.get('citizenship')}\n" f"Виза/статус: {d.get('visa_status')}\n" f"Языки: {d.get('languages')}\n" f"Образование/Профессия: {d.get('education')} / {d.get('occupation')}\n" f"Доход: {d.get('income_range')}\n" f"Религия: {d.get('religion')}\n" f"Семейное положение: {d.get('marital_status')}\n" f"Дети: {'да' if d.get('has_children') else 'нет'} {'(' + d.get('children_notes') + ')' if d.get('children_notes') else ''}\n" f"Курение/Алкоголь: {d.get('smoking')} / {d.get('alcohol')}\n" f"Здоровье: {d.get('health_notes')}\n" f"Хобби (теги): {d.get('hobbies_tags')}\n" f"Интересы (текст): {d.get('hobbies_free')}\n" f"Цель: {d.get('goal')}\n" f"Пожелания к партнёру: {d.get('partner_prefs')}\n" f"\nЕсли всё ок — напиши «подтверждаю». Иначе /cancel и /new." ) await update.message.reply_text(summary) return S_CONFIRM async def s_confirm(update, context): if (update.message.text or "").strip().lower() != "подтверждаю": await update.message.reply_text('Напиши «подтверждаю» для сохранения, либо /cancel.') return S_CONFIRM u = update.effective_user d = context.user_data with db_session() as s: cand = s.query(Candidate).filter(Candidate.telegram_id == u.id).first() if not cand: cand = Candidate(telegram_id=u.id, username=u.username or None) s.add(cand) cand.full_name = d.get("full_name") cand.gender = d.get("gender") cand.birth_date = d.get("birth_date") cand.height_cm = d.get("height_cm") cand.weight_kg = d.get("weight_kg") cand.country = d.get("country") cand.city = d.get("city") cand.citizenship = d.get("citizenship") cand.visa_status = d.get("visa_status") cand.languages = d.get("languages") cand.education = d.get("education") cand.occupation = d.get("occupation") cand.income_range = d.get("income_range") cand.religion = d.get("religion") cand.marital_status = d.get("marital_status") cand.has_children = d.get("has_children") cand.children_notes = d.get("children_notes") cand.smoking = d.get("smoking") cand.alcohol = d.get("alcohol") cand.health_notes = d.get("health_notes") cand.hobbies_tags = d.get("hobbies_tags") cand.hobbies_free = d.get("hobbies_free") cand.goal = d.get("goal") cand.partner_prefs = d.get("partner_prefs") cand.avatar_file_id = d.get("avatar_file_id") cand.gallery_file_ids = list_to_csv(d.get("gallery_file_ids", [])) cand.consent_personal = d.get("consent_personal", False) cand.consent_policy = d.get("consent_policy", False) cand.is_verified = False cand.is_active = True s.commit() await update.message.reply_text("Готово! Анкета сохранена. Свяжемся после модерации. Спасибо!") return ConversationHandler.END async def cancel(update, context): await update.message.reply_text("Ок, отменил. Для нового старта — /new") return ConversationHandler.END