from utils.texts import *
# /opt/trueconf_bot/lk/handlers.py
import re
import os
import csv
import logging  
import asyncio
import time
import smtplib
from datetime import datetime, timedelta
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

from ldap3 import Server, Connection, ALL
from ldap3.utils.conv import escape_filter_chars
from trueconf import Router, Message
from trueconf.types import FSInputFile

import config.config as config
import lk.payslip_service as ps  
import lk.documents_service as ds  
from utils.states import get_state, set_state, clear_state

# 🔌 Импортируем централизованную функцию сбора статистики из main
from utils.stats_logger import log_menu_stats

# 🔌 Импортируем функции безопасности из изолированного утилитного модуля 2FA
from utils.otp_service import generate_otp_code, send_otp_via_api, get_user_phone_from_ad

# 🔌 Импортируем вынесенную бизнес-логику запросов по CARD_ID
from lk.info_service import (
    get_vacation_records,
    get_rewards_text,
    get_sick_leaves_text,
    get_tabel_report_text,
    get_lk_profile_by_card_id,
    get_missions_records,
    get_transfers_records
)

logger = logging.getLogger(__name__)

# =========================================================
# ГЛОБАЛЬНЫЙ МАРШРУТИЗАТОР ЦЕЛЬНЫХ VK-ЭМОДЗИ И СЕССИЙ ПАГИНАЦИИ
# =========================================================
EMOJI_DIGITS = {
    "0": "0⃣",
    "1": "1⃣",
    "2": "2⃣",
    "3": "3⃣",
    "4": "4⃣",
    "5": "5⃣",
    "6": "6⃣",
    "7": "7⃣",
    "8": "8⃣",
    "9": "9⃣",
    "10": "🔟"
}

# Список из 7 справок строго по структуру портала
DOC_TYPES = {
    "1": "Справка о доходах физического лица (2-НДФЛ)",
    "2": "Справка об удержаниях за ДМС",
    "3": "Справка о средней заработной плате",
    "4": "Справка о получении единовременного пособия по рождению ребенка",
    "5": "Справка о получении ежемесячного пособия и назначении отпуска по уходу за ребенком",
    "6": "Справка об удержаниях по исполнительным листам",
    "7": "Копия трудовой книжки"
}

LK_DOCS_MENU_TEXT = (
    "📄 <b>Выберите тип документа:</b>\n\n" + 
    "\n".join([f"{EMOJI_DIGITS[k]} — {v}" for k, v in DOC_TYPES.items()]) + 
    f"\n\n<i>{EMOJI_DIGITS['9']} — Назад\n{EMOJI_DIGITS['0']} — В главное меню</i>"
)

USER_COOLDOWN_SEC = 1.5  
EMAIL_COOLDOWN_SEC = 10.0 

user_last_request = {}
last_email_time = 0.0
user_docs_session = {} 
user_page_sessions = {}

# 🔒 Репозиторий временных блокировок 2FA (хранит user_id: timestamp_разблокировки)
user_payslip_blocks = {}

router = Router()

# =========================================================
# СИСТЕМНЫЕ ФУНКЦИИ И ОШИБКИ
# =========================================================
def log_and_notify_error(user_id: str, action: str, error_details: str):
    global last_email_time
    log_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "lk", "logs")
    os.makedirs(log_dir, exist_ok=True)
    log_file = os.path.join(log_dir, "lk_errors.log")
    current_time = time.time()
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log_entry = f"[{timestamp}] User: {user_id} | Action: {action} | Error: {error_details}\n"
    
    with open(log_file, "a", encoding="utf-8") as f:
        f.write(log_entry)
        
    if current_time - last_email_time < EMAIL_COOLDOWN_SEC:
        return
    last_email_time = current_time

    try:
        msg = MIMEMultipart()
        msg['From'] = str(getattr(config, 'DEFAULT_EMAIL_FROM', 'bot@noreply.com'))
        to_emails = getattr(config, 'ALERTS_SUPPORT_EMAILS', 'admin@example.com')
        msg['To'] = ", ".join(to_emails) if isinstance(to_emails, list) else str(to_emails)
        msg['Subject'] = f"TrueConf Bot Error: ЛК - {action}"
        body = f"Обнаружена ошибка в ЛК.\nВремя: {timestamp}\nПользователь: {user_id}\nДействие: {action}\nДетали:\n{error_details}"
        msg.attach(MIMEText(body, 'plain', 'utf-8'))
        import lk.info_service as info_service
        with smtplib.SMTP(getattr(config, 'SMTP_SERVER', 'localhost'), int(getattr(config, 'SMTP_PORT', 25))) as server:
            server.send_message(msg)
    except Exception:
        pass

async def handle_lk_error(msg: Message, user_id: str, action: str, error_details: str):
    await asyncio.to_thread(log_and_notify_error, user_id, action, error_details)
    error_msg = lk_error_handling(EMOJI_DIGITS)
    await msg.answer(error_msg, parse_mode="html")

# =========================================================
# ИНТЕГРАЦИЯ С ACTIVE DIRECTORY ЧЕРЕЗ extensionAttribute2
# =========================================================
def get_card_id_from_ad(user_id: str) -> str:
    # Поиск CARD_ID по всем OU из AD_BASES
    from utils.ad_search import search_by_user_id
    try:
        entries = search_by_user_id(user_id, ['extensionAttribute2'])
        if not entries:
            raise Exception("Пользователь не найден в AD.")
        entry = entries[0]
        if 'extensionAttribute2' in entry and entry.extensionAttribute2.value:
            return str(entry.extensionAttribute2.value).strip()
        raise Exception("Поле extensionAttribute2 не заполнено в AD.")
    except Exception as e:
        raise Exception("AD Card Query Error: {}".format(e))

# =========================================================
# МОДНОЕ УНИВЕРСАЛЬНОЕ ЯДРО ПАГИНАЦИИ СПИСКОВ ДЛЯ ЧАТА
# =========================================================
async def send_paginated_page(msg: Message, user_id: str):
    session = user_page_sessions.get(user_id)
    if not session: return

    current_page = session["current_page"]
    per_page = session["per_page"]
    items = session["items"]
    
    total_items = len(items)
    total_pages = (total_items + per_page - 1) // per_page if total_items > 0 else 1
    
    start_idx = current_page * per_page
    end_idx = start_idx + per_page
    page_items = items[start_idx:end_idx]

    if session["type"] == "vacation":
        body_text = session["static_header"] + "\n📜 <b>История отпусков (полный архив):</b>\n"
        body_text += "\n".join(page_items) + "\n" if page_items else "• <i>История отпусков пуста</i>\n"
    elif session["type"] == "missions":
        body_text = "🧳 <b>Информация о командировках (архив):</b>\n\n"
        body_text += "\n\n".join(page_items) + "\n" if page_items else "• <i>Записи о командировках отсутствуют</i>\n"
    elif session["type"] == "transfers":
        body_text = "🔄 <b>История кадровых перемещений:</b>\n\n"
        body_text += "\n\n".join(page_items) + "\n" if page_items else "• <i>Записи о перемещениях отсутствуют</i>\n"

    body_text += f"\n📄 <b>Страница {current_page + 1} из {total_pages}</b>\n\n"

    nav_lines = []
    if current_page > 0: nav_lines.append(f"{EMOJI_DIGITS['7']} — Предыдущая страница")
    if current_page < total_pages - 1: nav_lines.append(f"{EMOJI_DIGITS['8']} — Следующая страница")
    nav_lines.append(f"{EMOJI_DIGITS['9']} — Назад")
    nav_lines.append(f"{EMOJI_DIGITS['0']} — В главное меню")

    body_text += "\n".join(nav_lines)
    await msg.answer(body_text, parse_mode="html")

# =========================================================
# ДИНАМИЧЕСКАЯ СБОРКА ГЛАВНОГО МЕНЮ ЛК
# =========================================================
async def send_dynamic_lk_main_menu(msg: Message, user_id: str):
    try:
        card_id = await asyncio.to_thread(get_card_id_from_ad, user_id)
        profile = await asyncio.to_thread(get_lk_profile_by_card_id, card_id)
        
        header = (
            f"👤 <b>Личный кабинет</b>\n\n"
            f"🏢 <b>Компания:</b> {profile['company']}\n"
            f"📂 <b>Отдел:</b> {profile['dept']}\n"
            f"💼 <b>Должность:</b> {profile['job']}\n"
            f"⏳ <b>Страховой стаж:</b> {profile['stag_o']}\n"
            f"📈 <b>Стаж на предприятии:</b> {profile['stag_p']}\n\n"
        )
    except Exception as e:
        logger.error(f"Не удалось собрать динамический заголовок ЛК: {e}")
        header = "👤 <b>Личный кабинет</b>\n\n"

    menu_text = lk_main_menu_text(header, EMOJI_DIGITS)
    await msg.answer(menu_text, parse_mode="html")

async def send_tabel_months_menu(msg: Message, user_id: str):
    now = datetime(2026, 6, 3) 
    prev_1 = now.replace(day=1) - timedelta(days=1)
    prev_2 = prev_1.replace(day=1) - timedelta(days=1)
    months_ru = {1: "Январь", 2: "Февраль", 3: "Март", 4: "Апрель", 5: "Май", 6: "Июнь", 7: "Июль", 8: "Август", 9: "Сентябрь", 10: "Октябрь", 11: "Ноябрь", 12: "Декабрь"}
    
    menu_text = lk_tabel_months_menu_text(prev_1, prev_2, months_ru, EMOJI_DIGITS)
    set_state(user_id, "LK_TABEL_MONTHS")
    await msg.answer(menu_text, parse_mode="html")

def wrapper_get_months(card_id: str) -> list:
    import lk.info_service as info_service
    conn = info_service.get_db_connection()
    cursor = conn.cursor()
    try: return ps.get_latest_payslip_months(cursor, card_id)
    finally: conn.close()

def wrapper_get_payslip(card_id: str, target_cmonth: int):
    import lk.info_service as info_service
    conn = info_service.get_db_connection()
    cursor = conn.cursor()
    try: return ps.build_payslip_data(cursor, card_id, target_cmonth)
    except Exception as e: raise Exception(f"Payslip Build Error: {e}")
    finally: conn.close()

async def send_payslip_months_menu(msg: Message, user_id: str, card_id: str):
    try:
        months_data = await asyncio.to_thread(wrapper_get_months, card_id)
        months_ru = {1: "Январь", 2: "Февраль", 3: "Март", 4: "Апрель", 5: "Май", 6: "Июнь", 7: "Июль", 8: "Август", 9: "Сентябрь", 10: "Октябрь", 11: "Ноябрь", 12: "Декабрь"}
        menu_text = lk_payslip_months_menu_text(months_data, months_ru, EMOJI_DIGITS)
        set_state(user_id, "LK_PAYSLIP_MONTHS")
        await msg.answer(menu_text, parse_mode="html")
    except Exception as e: raise Exception(str(e))

async def build_confirmation_text(user_id: str, doc: dict) -> str:
    confirm_txt = f"📝 <b>Подтверждение заказа:</b>\n\nДокумент: {doc['doc_name']}\nКоличество: {doc['quantity']} шт."
    if doc.get('period'): confirm_txt += f"\nУточнение: {doc['period']}"
    if getattr(config, 'DOCUMENTS_TEST_MODE', False):
        try:
            card_id = await asyncio.to_thread(get_card_id_from_ad, user_id)
            import lk.info_service as info_service
            with info_service.get_db_connection() as conn:
                cursor = conn.cursor()
                user_info = await asyncio.to_thread(ds.get_user_info_for_docs, cursor, card_id)
                
            orig_to = user_info.get('hr_email', '').strip()
            orig_cc = user_info.get('wb_email', '').strip() if doc['doc_id'] == "7" else ""
            requested_recipients = orig_to
            if orig_cc: requested_recipients += f" (Копия: {orig_cc})"
            test_email = getattr(config, 'DOCUMENTS_TARGET_EMAIL', 'mailonline@sibcem.ru')
            confirm_txt += f"\n\n⚠️ <b>Тест-режим.</b>\nКому: <code>{requested_recipients}</code>\nЗамена: <code>{test_email}</code>"
        except Exception as e: confirm_txt += f"\n\n⚠️ <b>Тест-режим.</b>\n(Ошибка БД: {e})"
    confirm_txt += f"\n\n<i>{EMOJI_DIGITS['1']} — Да, отправить\n{EMOJI_DIGITS['9']} — Назад\n{EMOJI_DIGITS['0']} — В главное меню</i>"
    return confirm_txt

# =========================================================
# ОСНОВНОЙ ОБРАБОТЧИК МАРШРУТОВ ЛК
# =========================================================
@router.message()
async def lk_router_handler(msg: Message):
    user_id = msg.from_user.id
    current_time = time.time()
    if current_time - user_last_request.get(user_id, 0) < USER_COOLDOWN_SEC: return
    user_last_request[user_id] = current_time
    
    current_state = get_state(user_id)
    nav_text = lk_nav_text(EMOJI_DIGITS)
    
    if not msg.text: return
    cmd = msg.text.strip().lower()
    
    for raw_num, emoji_num in EMOJI_DIGITS.items():
        if cmd == emoji_num:
            cmd = raw_num
            break
            
    if current_state is None:
        from utils.menu import MENU_MAP
        if MENU_MAP.get(cmd) == "LK":
            msg.handled = True
            set_state(user_id, "LK_MODE")
            await send_dynamic_lk_main_menu(msg, user_id)
            return
        return
    
    if current_state == "LK_MODE":
        msg.handled = True 
        if cmd == "0":
            log_menu_stats(user_id, "Личный кабинет", "Выход в главное меню")
            clear_state(user_id)
            from utils.menu import MENU_TEXT
            await msg.answer(MENU_TEXT, parse_mode="html")
        elif cmd == "1":
            block_expiry = user_payslip_blocks.get(user_id, 0)
            if time.time() < block_expiry:
                rem_minutes = int((block_expiry - time.time()) // 60) + 1
                await msg.answer(
                    f"❌ <b>Доступ временно заблокирован.</b>\n"
                    f"Вы трижды ввели неверный код подтверждения.\n"
                    f"Доступ к расчетным листам ограничен на {int(getattr(config, 'OTP_BLOCK_DURATION_SEC', 1800) // 60)} минут.\n\n"
                    f"⏱ Осталось подождать: <b>{rem_minutes} мин.</b>{nav_text}",
                    parse_mode="html"
                )
                return

            log_menu_stats(user_id, "Личный кабинет", "Расчётный лист")
            await msg.answer(lk_payslip_loading(EMOJI_DIGITS), parse_mode="html")
            try:
                card_id = await asyncio.to_thread(get_card_id_from_ad, user_id)
                await send_payslip_months_menu(msg, user_id, card_id)
            except Exception as e: await handle_lk_error(msg, user_id, "Меню расчетных листков", str(e))
        elif cmd == "2":
            log_menu_stats(user_id, "Личный кабинет", "Информация о днях отпуска")
            await msg.answer(lk_vacation_loading(EMOJI_DIGITS), parse_mode="html")
            try:
                card_id = await asyncio.to_thread(get_card_id_from_ad, user_id)
                static_header, history_lines = await asyncio.to_thread(get_vacation_records, card_id)
                user_page_sessions[user_id] = {"type": "vacation", "current_page": 0, "per_page": 5, "static_header": static_header, "items": history_lines}
                set_state(user_id, "LK_VACATION_VIEW")
                await send_paginated_page(msg, user_id)
            except Exception as e: await handle_lk_error(msg, user_id, "Справка по отпускам", str(e))
        elif cmd == "3":
            log_menu_stats(user_id, "Личный кабинет", "Больничные")
            await msg.answer(lk_sick_loading(EMOJI_DIGITS), parse_mode="html")
            try:
                card_id = await asyncio.to_thread(get_card_id_from_ad, user_id)
                sick_info = await asyncio.to_thread(get_sick_leaves_text, card_id)
                set_state(user_id, "LK_SICK_VIEW")
                await msg.answer(sick_info + nav_text, parse_mode="html")
            except Exception as e: await handle_lk_error(msg, user_id, "Просмотр больничных", str(e))
        elif cmd == "4":
            log_menu_stats(user_id, "Личный кабинет", "Командировки")
            await msg.answer(lk_missions_loading(EMOJI_DIGITS), parse_mode="html")
            try:
                card_id = await asyncio.to_thread(get_card_id_from_ad, user_id)
                mission_lines = await asyncio.to_thread(get_missions_records, card_id)
                user_page_sessions[user_id] = {"type": "missions", "current_page": 0, "per_page": 4, "items": mission_lines}
                set_state(user_id, "LK_MISSIONS_VIEW")
                await send_paginated_page(msg, user_id)
            except Exception as e: await handle_lk_error(msg, user_id, "Просмотр командировок", str(e))
        elif cmd == "5":
            log_menu_stats(user_id, "Личный кабинет", "Награды")
            await msg.answer(lk_rewards_loading(EMOJI_DIGITS), parse_mode="html")
            try:
                card_id = await asyncio.to_thread(get_card_id_from_ad, user_id)
                rewards_info = await asyncio.to_thread(get_rewards_text, card_id)
                set_state(user_id, "LK_REWARDS_VIEW")
                await msg.answer(rewards_info + nav_text, parse_mode="html")
            except Exception as e: await handle_lk_error(msg, user_id, "Запрос наград", str(e))
        elif cmd == "6":
            log_menu_stats(user_id, "Личный кабинет", "Переводы")
            await msg.answer(lk_transfers_loading(EMOJI_DIGITS), parse_mode="html")
            try:
                card_id = await asyncio.to_thread(get_card_id_from_ad, user_id)
                transfer_lines = await asyncio.to_thread(get_transfers_records, card_id)
                user_page_sessions[user_id] = {"type": "transfers", "current_page": 0, "per_page": 4, "items": transfer_lines}
                set_state(user_id, "LK_TRANSFERS_VIEW")
                await send_paginated_page(msg, user_id)
            except Exception as e: await handle_lk_error(msg, user_id, "Просмотр переводов", str(e))
        elif cmd == "7":
            log_menu_stats(user_id, "Личный кабинет", "Табель учёта рабочего времени")
            await send_tabel_months_menu(msg, user_id)
        elif cmd == "8":
            log_menu_stats(user_id, "Личный кабинет", "Справки")
            set_state(user_id, "LK_DOCS_MENU")
            await msg.answer(LK_DOCS_MENU_TEXT, parse_mode="html")
        else:
            # 🎯 ИСПРАВЛЕНО: Вместо спама копии меню ЛК выводим строгое предупреждение
            await msg.answer(UNKNOWN_MAIN_CMD_TEXT, parse_mode="html")

    elif current_state == "LK_TABEL_MONTHS":
        msg.handled = True
        if cmd == "0":
            clear_state(user_id)
            from utils.menu import MENU_TEXT
            await msg.answer(MENU_TEXT, parse_mode="html")
        elif cmd == "9":
            set_state(user_id, "LK_MODE")
            await send_dynamic_lk_main_menu(msg, user_id)
        elif cmd in ["1", "2"]:
            log_menu_stats(user_id, "Личный кабинет", "Просмотр табеля за месяц")
            await msg.answer(lk_tabel_loading(EMOJI_DIGITS), parse_mode="html")
            try:
                card_id = await asyncio.to_thread(get_card_id_from_ad, user_id)
                now = datetime(2026, 6, 3)
                target_date = now.replace(day=1) - timedelta(days=1) if cmd == "1" else (now.replace(day=1) - timedelta(days=1)).replace(day=1) - timedelta(days=1)
                tabel_report = await asyncio.to_thread(get_tabel_report_text, card_id, target_date.year, target_date.month)
                set_state(user_id, "LK_TABEL_VIEW")
                await msg.answer(tabel_report + nav_text, parse_mode="html")
            except Exception as e: await handle_lk_error(msg, user_id, "Формирование табеля", str(e))
        else:
            await msg.answer(UNKNOWN_MAIN_CMD_TEXT, parse_mode="html")

    elif current_state in ["LK_VACATION_VIEW", "LK_MISSIONS_VIEW", "LK_TRANSFERS_VIEW"]:
        msg.handled = True
        session = user_page_sessions.get(user_id)
        if cmd == "0":
            user_page_sessions.pop(user_id, None)
            clear_state(user_id)
            from utils.menu import MENU_TEXT
            await msg.answer(MENU_TEXT, parse_mode="html")
        elif cmd == "9":
            user_page_sessions.pop(user_id, None)
            set_state(user_id, "LK_MODE")
            await send_dynamic_lk_main_menu(msg, user_id)
        elif cmd == "7" and session and session["current_page"] > 0:
            session["current_page"] -= 1
            await send_paginated_page(msg, user_id)
        elif cmd == "8" and session:
            if session["current_page"] < (len(session["items"]) + session["per_page"] - 1) // session["per_page"] - 1:
                session["current_page"] += 1
                await send_paginated_page(msg, user_id)
        else:
            await msg.answer(UNKNOWN_MAIN_CMD_TEXT, parse_mode="html")

    elif current_state == "LK_PAYSLIP_AUTH":
        msg.handled = True
        session = user_page_sessions.get(user_id)
        
        if cmd == "0":
            user_page_sessions.pop(user_id, None)
            clear_state(user_id)
            from utils.menu import MENU_TEXT
            await msg.answer(MENU_TEXT, parse_mode="html")
            return
        elif cmd == "9":
            user_page_sessions.pop(user_id, None)
            set_state(user_id, "LK_PAYSLIP_MONTHS")
            try:
                card_id = await asyncio.to_thread(get_card_id_from_ad, user_id)
                await send_payslip_months_menu(msg, user_id, card_id)
            except Exception as e: await handle_lk_error(msg, user_id, "Возврат к меню месяцев", str(e))
            return

        if not session:
            await msg.answer("⚠️ <i>Сессия авторизации не найдена. Начните сначала.</i>", parse_mode="html")
            set_state(user_id, "LK_MODE")
            await send_dynamic_lk_main_menu(msg, user_id)
            return

        if time.time() > session.get("expires_at", 0):
            log_menu_stats(user_id, "Личный кабинет", "Ошибка 2FA: Истекло время жизни кода")
            user_page_sessions.pop(user_id, None)
            set_state(user_id, "LK_MODE")
            await msg.answer(
                f"⏱ <b>Время действия кода подтверждения изменилось или истекло.</b>\n"
                f"Пожалуйста, запросите расчетный лист заново.{nav_text}", 
                parse_mode="html"
            )
            await send_dynamic_lk_main_menu(msg, user_id)
            return

        if cmd == session.get("correct_code"):
            log_menu_stats(user_id, "Личный кабинет", "Успешная проверка 2FA расчетного листа")
            await msg.answer(lk_auth_success(EMOJI_DIGITS), parse_mode="html")
            
            try:
                card_id = await asyncio.to_thread(get_card_id_from_ad, user_id)
                target_cmonth = session["target_cmonth"]
                
                m_name = {1: "Январь", 2: "Февраль", 3: "Март", 4: "Апрель", 5: "Май", 6: "Июнь", 7: "Июль", 8: "Август", 9: "Сентябрь", 10: "Октябрь", 11: "Ноябрь", 12: "Декабрь"}
                tmonth = (target_cmonth - 1) % 12 + 1
                tyear = (target_cmonth - 1) // 12
                
                payslip_info, pdf_path = await asyncio.to_thread(wrapper_get_payslip, card_id, target_cmonth)
                
                set_state(user_id, "LK_PAYSLIP_VIEW")
                await msg.answer_document(
                    FSInputFile(pdf_path, filename=f"Расчетный_лист_{m_name.get(tmonth, '00')}_{tyear}.pdf"), 
                    caption=payslip_info + f"\n\n<i>{EMOJI_DIGITS['9']} — Назад к выбору месяца\n{EMOJI_DIGITS['0']} — В главное меню</i>", 
                    parse_mode="html"
                )
                if os.path.exists(pdf_path): os.remove(pdf_path)
            except Exception as e: 
                await handle_lk_error(msg, user_id, "Просмотр детализации после 2FA", str(e))
            finally:
                user_page_sessions.pop(user_id, None)
        else:
            session["attempts_left"] -= 1
            if session["attempts_left"] <= 0:
                log_menu_stats(user_id, "Личный кабинет", "Ошибка 2FA: Превышен лимит попыток ввода, бан")
                
                block_duration = int(getattr(config, 'OTP_BLOCK_DURATION_SEC', 1800))
                user_payslip_blocks[user_id] = time.time() + block_duration
                
                user_page_sessions.pop(user_id, None)
                set_state(user_id, "LK_MODE")
                await msg.answer(
                    f"❌ <b>Превышено максимальное количество попыток ввода кода.</b>\n"
                    f"Доступ к финансовым документам заблокирован на <b>{block_duration // 60} минут</b>.{nav_text}", 
                    parse_mode="html"
                )
                await send_dynamic_lk_main_menu(msg, user_id)
            else:
                await msg.answer(
                    f"⚠️ <b>Неверный код подтверждения.</b>\n"
                    f"Осталось попыток: {session['attempts_left']}. Попробуйте еще раз или вернитесь назад.{nav_text}", 
                    parse_mode="html"
                )

    elif current_state in ["LK_REWARDS_VIEW", "LK_PAYSLIP_MONTHS", "LK_SICK_VIEW", "LK_TABEL_VIEW", "LK_STUB_VIEW"]:
        msg.handled = True
        if cmd == "0":
            clear_state(user_id)
            from utils.menu import MENU_TEXT
            await msg.answer(MENU_TEXT, parse_mode="html")
        elif cmd == "9":
            if current_state == "LK_TABEL_VIEW": await send_tabel_months_menu(msg, user_id)
            else: set_state(user_id, "LK_MODE"); await send_dynamic_lk_main_menu(msg, user_id)
        elif current_state == "LK_PAYSLIP_MONTHS" and cmd.isdigit():
            try:
                card_id = await asyncio.to_thread(get_card_id_from_ad, user_id)
                months_data = await asyncio.to_thread(wrapper_get_months, card_id)
                idx = int(cmd) - 1
                
                if 0 <= idx < len(months_data):
                    target_cmonth = months_data[idx]
                    
                    await msg.answer(lk_auth_processing(EMOJI_DIGITS), parse_mode="html")
                    user_phone = await asyncio.to_thread(get_user_phone_from_ad, user_id)
                    
                    otp_code = generate_otp_code(4)
                    sms_success, channel_info = await send_otp_via_api(user_phone, otp_code)
                    
                    if sms_success:
                        log_menu_stats(user_id, "Личный кабинет", f"Запрос кода 2FA для расчетного листа ({channel_info})")
                        masked_phone = f"{user_phone[:5]}***{user_phone[-4:]}" if len(user_phone) > 8 else user_phone
                        
                        otp_ttl = int(getattr(config, 'OTP_TTL_SEC', 180))
                        max_attempts = int(getattr(config, 'OTP_MAX_ATTEMPTS', 3))
                        
                        user_page_sessions[user_id] = {
                            "correct_code": otp_code,
                            "target_cmonth": target_cmonth,
                            "expires_at": time.time() + otp_ttl,
                            "attempts_left": max_attempts
                        }
                        
                        set_state(user_id, "LK_PAYSLIP_AUTH")
                        await msg.answer(
                            f"🔐 <b>Двухфакторная авторизация</b>\n\n"
                            f"Код подтверждения отправлен <b>{channel_info}</b> на номер <code>{masked_phone}</code>.\n"
                            f"Пожалуйста, введите 4-значный код из сообщения в ответ:\n\n"
                            f"<i>Код действителен в течение {otp_ttl // 60} минут. У вас есть {max_attempts} попытки.</i>{nav_text}",
                            parse_mode="html"
                        )
                    else:
                        raise Exception(f"Внутренний каскадный шлюз недоступен: {channel_info}")
                else:
                    await msg.answer(UNKNOWN_MAIN_CMD_TEXT, parse_mode="html")
            except Exception as e: await handle_lk_error(msg, user_id, "Инициализация 2FA расчетного листа", str(e))
        else:
            await msg.answer(UNKNOWN_MAIN_CMD_TEXT, parse_mode="html")

    elif current_state == "LK_PAYSLIP_VIEW":
        msg.handled = True
        if cmd == "0": clear_state(user_id); from utils.menu import MENU_TEXT; await msg.answer(MENU_TEXT, parse_mode="html")
        elif cmd == "9":
            try: card_id = await asyncio.to_thread(get_card_id_from_ad, user_id); await send_payslip_months_menu(msg, user_id, card_id)
            except Exception as e: await handle_lk_error(msg, user_id, "Возврат в меню месяцев", str(e))
        else:
            await msg.answer(UNKNOWN_MAIN_CMD_TEXT, parse_mode="html")

    elif current_state == "LK_DOCS_MENU":
        msg.handled = True
        if cmd == "0": user_docs_session.pop(user_id, None); clear_state(user_id); from utils.menu import MENU_TEXT; await msg.answer(MENU_TEXT, parse_mode="html")
        elif cmd == "9": user_docs_session.pop(user_id, None); set_state(user_id, "LK_MODE"); await send_dynamic_lk_main_menu(msg, user_id)
        elif cmd in DOC_TYPES:
            user_docs_session[user_id] = {'doc_id': cmd, 'doc_name': DOC_TYPES[cmd]}
            log_menu_stats(user_id, "Личный кабинет", f"Выбор справки: {DOC_TYPES[cmd]}")
            set_state(user_id, "LK_DOCS_QUANTITY")
            await msg.answer(lk_docs_quantity_prompt(EMOJI_DIGITS), parse_mode="html")
        else: 
            # 🎯 ИСПРАВЛЕНО: Заменен спам списка документов на чистую ошибку ввода
            await msg.answer(UNKNOWN_MAIN_CMD_TEXT, parse_mode="html")

    elif current_state == "LK_DOCS_QUANTITY":
        msg.handled = True
        if cmd == "0": user_docs_session.pop(user_id, None); clear_state(user_id); from utils.menu import MENU_TEXT; await msg.answer(MENU_TEXT, parse_mode="html")
        elif cmd == "9": set_state(user_id, "LK_DOCS_MENU"); await msg.answer(LK_DOCS_MENU_TEXT, parse_mode="html")
        elif cmd.isdigit() and 0 < int(cmd) <= 5:
            user_docs_session[user_id]['quantity'] = int(cmd)
            doc_id = user_docs_session[user_id]['doc_id']
            
            if doc_id in ["1", "2", "6"]:
                set_state(user_id, "LK_DOCS_PERIOD")
                current_year = datetime.now().year
                years_text = lk_years_menu(current_year, EMOJI_DIGITS)
                await msg.answer(years_text, parse_mode="html")
            elif doc_id == "3":
                set_state(user_id, "LK_DOCS_PERIOD")
                await msg.answer(lk_docs_avg_salary_prompt(EMOJI_DIGITS), parse_mode="html")
            elif doc_id == "5":
                set_state(user_id, "LK_DOCS_PERIOD")
                await msg.answer(lk_docs_child_age_prompt(EMOJI_DIGITS), parse_mode="html")
            else: 
                user_docs_session[user_id]['period'] = ""
                set_state(user_id, "LK_DOCS_CONFIRM")
                confirm_txt = await build_confirmation_text(user_id, user_docs_session[user_id])
                await msg.answer(confirm_txt, parse_mode="html")
        else: 
            # 🎯 ИСПРАВЛЕНО: Вместо кастомной подсказки выводим единый стандарт
            await msg.answer(UNKNOWN_MAIN_CMD_TEXT, parse_mode="html")

    elif current_state == "LK_DOCS_PERIOD":
        msg.handled = True
        if cmd == "0": user_docs_session.pop(user_id, None); clear_state(user_id); from utils.menu import MENU_TEXT; await msg.answer(MENU_TEXT, parse_mode="html")
        elif cmd == "9": set_state(user_id, "LK_DOCS_QUANTITY"); await msg.answer(lk_docs_quantity_prompt(EMOJI_DIGITS), parse_mode="html")
        else:
            doc_id = user_docs_session[user_id]['doc_id']
            if doc_id in ["1", "2", "6"]:
                if cmd in ["1", "2", "3", "4", "5", "6", "7", "8"]:
                    user_docs_session[user_id]['period'] = f"за {datetime.now().year - (int(cmd) - 1)} год"
                else:
                    # 🎯 ИСПРАВЛЕНО: Убран спам меню годов при абракадабре
                    await msg.answer(UNKNOWN_MAIN_CMD_TEXT, parse_mode="html")
                    return
            elif doc_id == "5":
                if cmd == "1": user_docs_session[user_id]['period'] = "до 1.5 лет"
                elif cmd == "2": user_docs_session[user_id]['period'] = "до 3 лет"
                else: 
                    # 🎯 ИСПРАВЛЕНО: Убран спам меню возраста при некорректном вводе
                    await msg.answer(UNKNOWN_MAIN_CMD_TEXT, parse_mode="html")
                    return
            elif doc_id == "3":
                user_docs_session[user_id]['period'] = f"за {cmd} мес."
                
            set_state(user_id, "LK_DOCS_CONFIRM")
            confirm_txt = await build_confirmation_text(user_id, user_docs_session[user_id])
            await msg.answer(confirm_txt, parse_mode="html")

    elif current_state == "LK_DOCS_CONFIRM":
        msg.handled = True
        if cmd == "0": user_docs_session.pop(user_id, None); clear_state(user_id); from utils.menu import MENU_TEXT; await msg.answer(MENU_TEXT, parse_mode="html")
        elif cmd == "9":
            doc_id = user_docs_session.get(user_id, {}).get('doc_id')
            if doc_id in ["1", "2", "3", "5", "6"]:
                set_state(user_id, "LK_DOCS_PERIOD")
                if doc_id in ["1", "2", "6"]:
                    current_year = datetime.now().year
                    years_text = lk_years_menu(current_year, EMOJI_DIGITS)
                    await msg.answer(years_text, parse_mode="html")
                elif doc_id == "3": await msg.answer(lk_docs_avg_salary_prompt(EMOJI_DIGITS), parse_mode="html")
                elif doc_id == "5": await msg.answer(lk_docs_child_age_prompt(EMOJI_DIGITS), parse_mode="html")
            else: set_state(user_id, "LK_DOCS_QUANTITY"); await msg.answer(lk_docs_quantity_prompt(EMOJI_DIGITS), parse_mode="html")
        elif cmd == "1":
            await msg.answer("⏳ <i>Отправка заявки...</i>", parse_mode="html")
            try:
                doc = user_docs_session[user_id]
                card_id = await asyncio.to_thread(get_card_id_from_ad, user_id)
                
                import lk.info_service as info_service
                with info_service.get_db_connection() as conn:
                    cursor = conn.cursor()
                    user_info = await asyncio.to_thread(ds.get_user_info_for_docs, cursor, card_id)

                success = await asyncio.to_thread(ds.generate_mail_message, doc['doc_name'], doc['quantity'], user_info, doc.get('period', ''))
                if success:
                    log_menu_stats(user_id, "Личный кабинет", f"Заказ справки: {doc['doc_name']}")
                    await msg.answer(f"✅ <b>Ваша заявка успешно отправлена!</b>\nСпециалисты свяжутся с вами по готовности документа.\n\n<i>{EMOJI_DIGITS['9']} — Назад в список справок\n{EMOJI_DIGITS['0']} — В главное меню</i>", parse_mode="html")
                    set_state(user_id, "LK_DOCS_SUCCESS")  
                else: raise Exception("Внутренняя ошибка SMTP при отправке письма")
            except Exception as e: await handle_lk_error(msg, user_id, f"Заказ справки {user_docs_session.get(user_id, {}).get('doc_id')}", str(e)); set_state(user_id, "LK_MODE")
            finally: user_docs_session.pop(user_id, None)
        else: 
            # 🎯 ИСПРАВЛЕНО: Удален спам-текст подтверждения
            await msg.answer(UNKNOWN_MAIN_CMD_TEXT, parse_mode="html")

    elif current_state == "LK_DOCS_SUCCESS":
        msg.handled = True
        if cmd == "0": clear_state(user_id); from utils.menu import MENU_TEXT; await msg.answer(MENU_TEXT, parse_mode="html")
        elif cmd == "9": set_state(user_id, "LK_DOCS_MENU"); await msg.answer(LK_DOCS_MENU_TEXT, parse_mode="html")
        else: 
            # 🎯 ИСПРАВЛЕНО: Унифицировано под общий шаблон ошибки
            await msg.answer(UNKNOWN_MAIN_CMD_TEXT, parse_mode="html")