# Системные импорты
import asyncio, logging, os, re, random, sqlite3
from datetime import datetime, time, timedelta
from io import BytesIO
from pathlib import Path
from zoneinfo import ZoneInfo

# Стороние импорты
import requests
from PIL import Image, ImageDraw, ImageFont
from aiogram import Bot, Dispatcher, F, types
from aiogram.filters import Command, CommandStart
from aiogram.enums import ParseMode
from aiogram.types import (
    BufferedInputFile, Message, BotCommand, FSInputFile,
    BotCommandScopeDefault, BotCommandScopeAllPrivateChats, BotCommandScopeAllGroupChats
)
from aiogram.client.default import DefaultBotProperties

# Локальные импорты
from AI.talk_handler import handle_talk, generate_autoreply, push_message
from zparser import get_military_data
import config 

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# --- Инициализация и вспомогательные функции ---

def init_penis_db() -> None:
    """Инициализация БД для миниигры."""
    Path(config.PENIS_DB_PATH).parent.mkdir(parents=True, exist_ok=True)
    with sqlite3.connect(config.PENIS_DB_PATH) as conn:
        conn.execute(
            """
            CREATE TABLE IF NOT EXISTS penis_stats (
                user_id INTEGER PRIMARY KEY,
                display_name TEXT NOT NULL,
                length REAL NOT NULL,
                last_used_ts INTEGER
            )
            """
        )
        conn.execute("CREATE INDEX IF NOT EXISTS idx_penis_stats_length ON penis_stats(length DESC)")
        conn.commit()

def templates_list_text() -> str:
    """Список макетов для подсказки."""
    parts = []
    for tid in sorted(config.TEMPLATES.keys()):
        label = config.TEMPLATES[tid].get("label", "")
        parts.append(f"{tid} — {label}" if label else tid)
    return ", ".join(parts) if parts else "нет макетов"

def minutes_until_break(now: datetime | None = None) -> str:
    """Логика расписания перекуров."""
    if now is None:
        now = datetime.now(ZoneInfo(config.DEFAULT_TZ))
    elif now.tzinfo is None:
        now = now.replace(tzinfo=ZoneInfo(config.DEFAULT_TZ))
    
    day = now.weekday()
    today_schedule = config.SCHEDULE.get(day, [])
    if not today_schedule:
        return "Сегодня кайфуем дома, перекур не нужен."

    def dt_for(t: time) -> datetime:
        return now.replace(hour=t.hour, minute=t.minute, second=0, microsecond=0)

    for start_t, end_t, br_minutes in today_schedule:
        start_dt = dt_for(start_t)
        end_dt = dt_for(end_t)

        if now < start_dt or (start_dt <= now < end_dt):
            minutes = int((end_dt - now).total_seconds() // 60)
            return f"{minutes} минут до перекура"

        if br_minutes:
            break_end = end_dt + timedelta(minutes=br_minutes)
            if end_dt <= now < break_end:
                remaining = int((break_end - now).total_seconds() // 60)
                return f"Идёт перекур, осталось {remaining} минут"

    return "Пары закончились, перекур только завтра."

# --- Работа с изображениями (Макеты и Мемы) ---

def load_font(size: int) -> ImageFont.FreeTypeFont:
    if not config.FONT_PATH.exists():
        raise FileNotFoundError(f"Шрифт не найден: {config.FONT_PATH}")
    return ImageFont.truetype(str(config.FONT_PATH), size=size)

def wrap_text_to_width(text: str, font: ImageFont.FreeTypeFont, max_width: int, draw: ImageDraw.ImageDraw, stroke_width: int) -> list[str]:
    words = text.split()
    lines, current = [], []
    for word in words:
        tentative = " ".join(current + [word]) if current else word
        bbox = draw.textbbox((0, 0), tentative, font=font, stroke_width=stroke_width)
        if bbox[2] - bbox[0] <= max_width:
            current.append(word)
        else:
            if current: lines.append(" ".join(current))
            current = [word]
    if current: lines.append(" ".join(current))
    return lines

def parse_up_down(text: str) -> tuple[str, str]:
    """Парсит формат /gen_mem Up:текст Down:текст"""
    up = ""
    down = ""
    # Очищаем текст от самой команды
    text_normalized = re.sub(r'^/\w+\s*', '', text, flags=re.IGNORECASE).replace("\n", " ")
    
    up_match = re.search(r"(?is)up:(.*?)(?=\s+down:|$)", text_normalized)
    down_match = re.search(r"(?is)down:(.*)$", text_normalized)
    
    if up_match:
        up = up_match.group(1).strip()
    if down_match:
        down = down_match.group(1).strip()
        
    # Если меток нет, по старинке: первое слово вверх, остальное вниз
    if not up and not down:
        parts = text_normalized.split(None, 1)
        if parts:
            up = parts[0].strip()
            if len(parts) > 1:
                down = parts[1].strip()
    return up, down

def fit_text_in_box(text: str, max_width: int, max_height: int, start_size: int = 72, min_size: int = 14):
    draw_dummy = ImageDraw.Draw(Image.new("RGB", (1, 1)))
    for size in range(start_size, min_size - 1, -2):
        font = load_font(size)
        stroke_width = max(2, size // 12)
        lines = wrap_text_to_width(text, font, max_width, draw_dummy, stroke_width)
        if not lines: continue
        
        line_heights = [draw_dummy.textbbox((0, 0), ln, font=font, stroke_width=stroke_width)[3] - 
                        draw_dummy.textbbox((0, 0), ln, font=font, stroke_width=stroke_width)[1] for ln in lines]
        total_height = sum(line_heights) + max(0, len(lines) - 1) * max(6, size // 6)
        if total_height <= max_height:
            return font, lines, stroke_width, max(6, size // 6)
    
    font = load_font(min_size)
    return font, [text], max(1, min_size // 12), max(4, min_size // 6)

def draw_multiline(draw: ImageDraw.ImageDraw, lines: list[str], position: tuple[int, int], font: ImageFont.FreeTypeFont,
                   stroke_width: int, line_spacing: int, align_center: bool = False, max_width: int | None = None):
    x, y = position
    for line in lines:
        bbox = draw.textbbox((0, 0), line, font=font, stroke_width=stroke_width)
        line_width = bbox[2] - bbox[0]
        draw_x = x
        if align_center and max_width is not None:
            draw_x = x + (max_width - line_width) // 2
        draw.text((draw_x, y), line, font=font, fill="white", stroke_width=stroke_width, stroke_fill="black")
        y += (bbox[3] - bbox[1]) + line_spacing

def calculate_font_for_width(text: str, image_width: int, max_ratio: float = 0.09, min_size: int = 18):
    start_size = max(int(image_width * max_ratio), min_size)
    draw_dummy = ImageDraw.Draw(Image.new("RGB", (image_width, image_width)))
    for size in range(start_size, min_size - 1, -2):
        font = load_font(size)
        stroke_width = max(2, size // 12)
        wrapped = wrap_text_to_width(text, font, int(image_width * 0.95), draw_dummy, stroke_width)
        if wrapped:
            height = 0
            for ln in wrapped:
                bbox = draw_dummy.textbbox((0, 0), ln, font=font, stroke_width=stroke_width)
                height += bbox[3] - bbox[1]
            height += max(0, len(wrapped) - 1) * max(6, size // 6)
            if height <= image_width * 0.35:
                return font, wrapped, stroke_width, max(6, size // 6)

    font = load_font(min_size)
    stroke_width = max(1, min_size // 12)
    wrapped = wrap_text_to_width(text, font, int(image_width * 0.95), draw_dummy, stroke_width)
    return font, wrapped or [text], stroke_width, max(4, min_size // 6)

def generate_maket(text: str, template_id: str = "1") -> BytesIO:
    tpl = config.TEMPLATES.get(template_id)
    if not tpl or not tpl["path"].exists():
        raise FileNotFoundError(f"Шаблон {template_id} не найден")

    with Image.open(tpl["path"]).convert("RGB") as img:
        anchor, padding = tpl["anchor"], tpl["padding"]
        max_w, max_h = img.width - anchor[0] - padding, img.height - anchor[1] - padding
        
        font, lines, stroke, spacing = fit_text_in_box(text, max_w, max_h)
        draw = ImageDraw.Draw(img)
        
        y_offset = anchor[1]
        for line in lines:
            draw.text((anchor[0], y_offset), line, font=font, fill="white", stroke_width=stroke, stroke_fill="black")
            bbox = draw.textbbox((0, 0), line, font=font, stroke_width=stroke)
            y_offset += (bbox[3] - bbox[1]) + spacing

        output = BytesIO()
        img.save(output, format="PNG")
        output.seek(0)
        return output

def generate_meme(img: Image.Image, top_text: str, bottom_text: str) -> BytesIO:
    img = img.convert("RGB")
    draw = ImageDraw.Draw(img)
    width, height = img.size
    margin_y = int(height * 0.04)

    if top_text:
        font, lines, stroke_width, spacing = calculate_font_for_width(top_text, width)
        draw_multiline(
            draw,
            lines,
            (int(width * 0.025), margin_y),
            font,
            stroke_width,
            spacing,
            align_center=True,
            max_width=int(width * 0.95),
        )

    if bottom_text:
        font, lines, stroke_width, spacing = calculate_font_for_width(bottom_text, width)
        block_height = 0
        for ln in lines:
            bbox = draw.textbbox((0, 0), ln, font=font, stroke_width=stroke_width)
            block_height += bbox[3] - bbox[1]
        block_height += max(0, len(lines) - 1) * spacing
        start_y = height - margin_y - block_height
        draw_multiline(
            draw,
            lines,
            (int(width * 0.025), start_y),
            font,
            stroke_width,
            spacing,
            align_center=True,
            max_width=int(width * 0.95),
        )

    output = BytesIO()
    img.save(output, format="JPEG", quality=95)
    output.seek(0)
    return output



def minutes_until_next_lesson(now: datetime | None = None) -> str:
    if now is None:
        now = datetime.now(ZoneInfo(config.DEFAULT_TZ))
    elif now.tzinfo is None:
        now = now.replace(tzinfo=ZoneInfo(config.DEFAULT_TZ))

    lesson_days = [1, 3]  # вторник и четверг
    lesson_start = time(9, 50)
    lesson_end = time(11, 25)
    day = now.weekday()

    def lesson_start_for(day_offset: int) -> datetime:
        target = now + timedelta(days=day_offset)
        return target.replace(hour=lesson_start.hour, minute=lesson_start.minute, second=0, microsecond=0)

    def lesson_end_today() -> datetime:
        return now.replace(hour=lesson_end.hour, minute=lesson_end.minute, second=0, microsecond=0)

    if day in lesson_days:
        start_today = lesson_start_for(0)
        end_today = lesson_end_today()
        if start_today <= now < end_today:
            for i in range(1, 8):
                next_day = (day + i) % 7
                if next_day in lesson_days:
                    next_dt = lesson_start_for(i)
                    break
            delta = next_dt - now
            total_seconds = int(delta.total_seconds())
            days = total_seconds // 86400
            hours = (total_seconds % 86400) // 3600
            minutes = (total_seconds % 3600) // 60
            percent = random.randint(0, 100)
            return (
                f"Сегодня твой мозг сгнил на {percent}%\n"
                f"Мозг начнет догнивать через {days} дн {hours} ч {minutes} мин"
            )

    for i in range(0, 8):
        next_day = (day + i) % 7
        if next_day in lesson_days:
            candidate = lesson_start_for(i)
            if candidate > now:
                delta = candidate - now
                break

    total_seconds = int(delta.total_seconds())
    days = total_seconds // 86400
    hours = (total_seconds % 86400) // 3600
    minutes = (total_seconds % 3600) // 60
    return f"Твой мозг окончательно сгниет, а очко порвется через: {days} дн {hours} ч {minutes} мин"

# --- Игровая логика (/penis) ---

def play_penis(user_id: int, user_name: str | None = None, now: datetime | None = None) -> tuple[bool, str]:
    if now is None:
        now = datetime.now(ZoneInfo(config.DEFAULT_TZ))
    elif now.tzinfo is None:
        now = now.replace(tzinfo=ZoneInfo(config.DEFAULT_TZ))
    now_ts = int(now.timestamp())
    display_name = user_name.strip() if user_name and user_name.strip() else f"user_{user_id}"

    try:
        with sqlite3.connect(config.PENIS_DB_PATH) as conn:
            conn.row_factory = sqlite3.Row
            row = conn.execute(
                "SELECT length, last_used_ts, display_name FROM penis_stats WHERE user_id = ?",
                (user_id,),
            ).fetchone()

            if row is None:
                current_length = config.PENIS_START_LENGTH
                last_used_ts = None
            else:
                current_length = float(row["length"])
                last_used_ts = row["last_used_ts"]
                stored_name = row["display_name"]
                if not user_name and isinstance(stored_name, str) and stored_name.strip():
                    display_name = stored_name.strip()

            if last_used_ts is not None:
                next_ts = int(last_used_ts) + config.PENIS_COOLDOWN_SECONDS
                if now_ts < next_ts:
                    if row is not None and row["display_name"] != display_name:
                        conn.execute(
                            "UPDATE penis_stats SET display_name = ? WHERE user_id = ?",
                            (display_name, user_id),
                        )
                    left_seconds = next_ts - now_ts
                    hours = left_seconds // 3600
                    minutes = (left_seconds % 3600) // 60
                    conn.commit()
                    return False, f"Сегодня уже кидал. Попробуй через {hours} ч {minutes} мин."

            delta = round(random.uniform(config.PENIS_MIN_DELTA, config.PENIS_MAX_DELTA), 1)
            sign = random.choice([-1, 1])
            new_length = round(max(0.1, current_length + sign * delta), 1)

            if row is None:
                conn.execute(
                    "INSERT INTO penis_stats(user_id, display_name, length, last_used_ts) VALUES(?, ?, ?, ?)",
                    (user_id, display_name, new_length, now_ts),
                )
            else:
                conn.execute(
                    "UPDATE penis_stats SET display_name = ?, length = ?, last_used_ts = ? WHERE user_id = ?",
                    (display_name, new_length, now_ts, user_id),
                )
            conn.commit()
    except sqlite3.Error:
        logger.exception("Failed to process /penis")
        return False, "Ошибка БД. Попробуй позже."

    sign_text = "+" if sign > 0 else "-"
    return (
        True,
        f"📏 Изменение: {sign_text}{delta:.1f} см\n"
        f"Текущая длина: {new_length:.1f} см\n"
        "Следующая попытка через 24 часа.",
    )

def format_penis_user_name(user: types.User) -> str:
    if user.username:
        return f"@{user.username}"
    if user.full_name:
        return user.full_name
    return f"user_{user.id}"

def build_penis_top(limit: int = config.PENIS_TOP_LIMIT) -> str:
    try:
        with sqlite3.connect(config.PENIS_DB_PATH) as conn:
            conn.row_factory = sqlite3.Row
            rows = conn.execute(
                """
                SELECT user_id, display_name, length
                FROM penis_stats
                ORDER BY length DESC, user_id ASC
                LIMIT ?
                """,
                (limit,),
            ).fetchall()
    except sqlite3.Error:
        logger.exception("Failed to build /top_penis")
        return "Не удалось загрузить топ. Ошибка БД."

    if not rows:
        return "🏆 Топ пуст. Сначала пусть кто-то крутнет /penis."

    medals = {1: "🥇", 2: "🥈", 3: "🥉"}
    lines = ["🏆 Топ по длине:"]
    for index, row in enumerate(rows, start=1):
        user_id = int(row["user_id"])
        length = float(row["length"])
        name = row["display_name"] if isinstance(row["display_name"], str) else f"user_{user_id}"
        name = name.strip() if name.strip() else f"user_{user_id}"
        prefix = medals.get(index, f"{index}.")
        lines.append(f"{prefix} {name} — {length:.1f} см")
    return "\n".join(lines)

# --- Хендлеры aiogram ---

async def handle_help(message: Message):
    await message.answer(config.get_hint_text(templates_list_text()))

async def handle_start(message: Message):
    await message.answer("Соси")

async def handle_break_cmd(message: Message):
    await message.answer(minutes_until_break())

async def handle_penis_cmd(message: Message):
    if not message.from_user:
        await message.answer("Команда доступна только пользователям.")
        return
    display_name = format_penis_user_name(message.from_user)
    await message.answer(play_penis(message.from_user.id, display_name)[1])

async def handle_top_penis_cmd(message: Message):
    await message.answer(build_penis_top())

async def handle_size_cmd(message: Message):
    size = round(random.uniform(0.1, 35.0), 1)
    if size < 5:
        status = "СВОи"
    elif size < 10:
        status = "Неплохой"
    elif size < 18:
        status = "Шлюхи ахуевают при виде тебя"
    elif size < 25:
        status = "Хуек с коробок"
    elif size < 32:
        status = "Пиздабол"
    else:
        status = "Говноед"
    await message.answer(f"📏 Твой хуек: {size} см\nТы: {status}")

async def handle_zvetok_cmd(message: Message):
    await message.answer(minutes_until_next_lesson())

async def handle_prices_cmd(message: Message):
    def fetch():
        return requests.get(
            "https://bebrik.xyz/magnit/products",
            headers={"X-Token": "sisi"},
            timeout=10,
        )
    try:
        resp = await asyncio.to_thread(fetch)
        if resp.status_code != 200:
            await message.answer("пошел нахуй")
            return
        data = resp.json()
        if not data:
            await message.answer("иди нахуй")
            return
        text = "смерть в 25:\n\n"
        for item in data:
            name = item.get("name")
            price = item.get("price")
            text += f"{name} — {price} ₽\n"
        await message.answer(text[:4000])
    except Exception:
        await message.answer("unable to connect x-server гандон")

async def handle_fetch_cmd(message: Message):
    def fetch():
        return requests.get(
            "https://mirror.porno4free.ru/magnit/magnit/prices/all",
            headers={"x-token": "secret_token"},
            timeout=10,
        )
    try:
        resp = await asyncio.to_thread(fetch)
        if resp.status_code != 200:
            await message.reply("пошел нахуй")
            return
        data = resp.json()
        if not isinstance(data, dict) or not data:
            await message.reply("иди нахуй")
            return
        lines = ["смерть в 25:"]
        for group in sorted(data.keys()):
            items = data.get(group)
            lines.append(f"\n{group}:")
            if not isinstance(items, list):
                continue
            for item in items:
                name = item.get("name")
                price = item.get("price")
                if not name or price is None:
                    continue
                lines.append(f"{name} — {price} ₽")
        await message.answer("\n".join(lines)[:4000])
    except Exception:
        await message.answer("unable to connect x-server гандон")

async def handle_svodka_cmd(message: Message):
    data = await asyncio.to_thread(get_military_data, "creamy_caprice", 5)
    if data["status"] == "success":
        response = (
            f"в период с {data['dates']} хохлы в среднем проебывали по {data['pace']} км² в сутки, "
            f"а за день ВС РФ лишала чубатых в среднем по {data['total']} км²"
        )
        await message.answer(response)
    else:
        await message.answer(f"Хохлы ответят за это...: {data['message']}")

async def handle_maket_cmd(message: Message):
    parts = message.text.split(maxsplit=2)
    if len(parts) == 1:
        await message.reply(f"Доступные макеты: {templates_list_text()}\nФормат: /maket «id» «текст»")
        return
    if len(parts) < 3:
        await message.reply(
            f"Формат: /maket «id» «текст»\nДоступные макеты: {templates_list_text()}\n\n"
            + config.get_hint_text(templates_list_text())
        )
        return
    template_id = parts[1]
    if template_id not in config.TEMPLATES:
        await message.reply(f"Такого макета нет. Доступные: {templates_list_text()}")
        return
    text = parts[2].strip()
    if not text:
        await message.reply("Нужен текст после номера макета.")
        return
    try:
        output = await asyncio.to_thread(generate_maket, text, template_id)
        await message.reply_photo(BufferedInputFile(output.getvalue(), filename="maket.png"))
    except Exception as exc:
        logger.exception("Failed to generate maket")
        await message.reply(f"Не получилось: {exc}")

async def handle_gen_mem(message: Message, bot: Bot):
    photo_msg = message if message.photo else (message.reply_to_message if message.reply_to_message and message.reply_to_message.photo else None)
    if not photo_msg:
        await message.reply("Еблан, /gen_mem Up: Down:")
        return
    full_text = message.text or ""
    top_text, bottom_text = parse_up_down(full_text)
    if not top_text and not bottom_text:
        await message.reply("Еблан, /gen_mem Up: Down:")
        return
    try:
        file = await bot.get_file(photo_msg.photo[-1].file_id)
        buf = BytesIO()
        await bot.download_file(file.file_path, destination=buf)
        buf.seek(0)
        img = Image.open(buf)
        output = await asyncio.to_thread(generate_meme, img, top_text, bottom_text)
    except Exception as exc:
        logger.exception("Failed to render meme")
        await message.reply(f"Не получилось сделать мем: {exc}")
        return
    await message.reply_photo(BufferedInputFile(output.getvalue(), filename="meme.jpg"))

_last_autoreply_ts = 0

async def handle_keywords(message: Message):
    global _last_autoreply_ts
    if not message.text or message.text.startswith('/'):
        return
    text_lower = message.text.lower()
    chat_id = message.chat.id
    user_name = (message.from_user.first_name or "кент") if message.from_user else "кент"

    if any(re.search(rf'\b{word}\b', text_lower) for word in config.KEYWORDS):
        audio_path = config.BASE_DIR / "maket" / "Radiohead - Creep.mp3"
        if audio_path.exists():
            await message.answer_audio(
                audio=FSInputFile(audio_path),
                caption="I'm a creep, I'm a weirdo...",
            )
        return

    # Записываем каждое обычное сообщение в историю чата
    await asyncio.to_thread(push_message, chat_id, "user", user_name, message.text)

    import time as _time
    now = _time.time()
    triggered = any(trigger in text_lower for trigger in config.AUTOREPLY_TRIGGERS)

    should_reply = triggered or (
        random.random() < config.AUTOREPLY_CHANCE
        and (now - _last_autoreply_ts) > config.AUTOREPLY_COOLDOWN
    )

    if should_reply:
        try:
            await message.bot.send_chat_action(chat_id=chat_id, action="typing")
            response = await generate_autoreply(chat_id, message.text, user_name)
            if response:
                await asyncio.to_thread(push_message, chat_id, "assistant", "бот", response)
                await message.reply(response, parse_mode=None)
                _last_autoreply_ts = now
        except Exception:
            logger.exception("Autoreply failed")

# --- Запуск ---

def main():
    token = os.getenv("BOT_TOKEN")
    if not token: raise RuntimeError("BOT_TOKEN не задан!")

    init_penis_db()
    bot = Bot(token=token, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
    dp = Dispatcher()

    # Регистрация хендлеров
    dp.message.register(handle_start, CommandStart())
    dp.message.register(handle_help, Command("help"))
    dp.message.register(handle_break_cmd, Command("break"))
    dp.message.register(handle_maket_cmd, Command("maket"))
    dp.message.register(handle_size_cmd, Command("size"))
    dp.message.register(handle_penis_cmd, Command("penis"))
    dp.message.register(handle_top_penis_cmd, Command("top_penis"))
    dp.message.register(handle_prices_cmd, Command("prices"))
    dp.message.register(handle_fetch_cmd, Command("fetch"))
    dp.message.register(handle_zvetok_cmd, Command("zvetok"))
    dp.message.register(handle_talk, Command("talk"))
    dp.message.register(handle_svodka_cmd, Command("svodka"))
    dp.message.register(handle_gen_mem, Command("gen_mem"))
    dp.message.register(handle_keywords, F.text)

    async def on_startup(bot: Bot):
        try:
            commands = [
                BotCommand(command="start", description="Приветствие"),
                BotCommand(command="help", description="Подсказки по командам"),
                BotCommand(command="maket", description="Вписать текст в макет 1"),
                BotCommand(command="gen_mem", description="Мем из фото (ответом)"),
                BotCommand(command="break", description="Сколько минут до перекура"),
                BotCommand(command="size", description="хуй"),
                BotCommand(command="penis", description="миниигра 1 раз в 24ч"),
                BotCommand(command="top_penis", description="топ по длине"),
                BotCommand(command="prices", description="цены x-server"),
                BotCommand(command="fetch", description="цены (новый источник)"),
                BotCommand(command="zvetok", description="Цветянский бля"),
                BotCommand(command="talk", description="Побазарить"),
                BotCommand(command="svodka", description="СВО: итоги"),
            ]
            scopes = (
                BotCommandScopeDefault(),
                BotCommandScopeAllPrivateChats(),
                BotCommandScopeAllGroupChats(),
            )
            for scope in scopes:
                await bot.set_my_commands(commands, scope=scope)
        except Exception:
            logger.warning("Не удалось выставить команды, но бот продолжит работу.")

    dp.startup.register(on_startup)
    logger.info("Бот запущен...")
    asyncio.run(dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types()))

if __name__ == "__main__":
    main()
