import asyncio
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Any, Optional

import pytz
from aiogram import Bot, Router
from sqlalchemy import select, text, update
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker

from config import (
    NOTIFICATION_TIME,
    NOTIFY_10H_ENABLED,
    NOTIFY_10H_HOURS,
    NOTIFY_24H_ENABLED,
    NOTIFY_24H_HOURS,
    NOTIFY_DELETE_DELAY,
    NOTIFY_DELETE_KEY,
    NOTIFY_HOT_LEADS,
    NOTIFY_INACTIVE_TRAFFIC,
    NOTIFY_RENEW,
    NOTIFY_RENEW_EXPIRED,
    TRIAL_TIME_DISABLE,
)
from core.bootstrap import MODES_CONFIG, NOTIFICATIONS_CONFIG
from database import (
    add_notification,
    check_notification_time,
    check_notifications_bulk,
    delete_key,
    delete_notification,
    get_all_keys,
    get_balance,
    get_last_notification_time,
    update_balance,
    update_key_expiry,
    update_key_tariff,
)
from database.models import Key, Tariff, User
from database.tariffs import (
    check_tariff_exists,
    get_tariff_by_id,
    get_tariffs_for_cluster,
)
from handlers.keys.operations import delete_key_from_cluster, renew_key_in_cluster
from handlers.notifications.notify_kb import (
    build_change_tariff_kb,
    build_notification_expired_kb,
    build_notification_kb,
)
from handlers.tariffs.tariff_display import GB, get_effective_limits_for_key, resolve_price_to_charge
from handlers.texts import (
    KEY_CANNOT_RENEW_CURRENT,
    KEY_DELETED_MSG,
    KEY_EXPIRED_DELAY_MSG,
    KEY_EXPIRED_NO_DELAY_MSG,
    KEY_EXPIRY,
    get_renewal_message,
)
from handlers.utils import format_hours, format_minutes, get_russian_month
from hooks.hooks import run_hooks
from logger import logger

from .hot_leads_notifications import notify_hot_leads
from .notify_utils import prepare_key_expiry_data, send_messages_with_limit, send_notification
from .special_notifications import notify_inactive_trial_users, notify_users_no_traffic


router = Router()
moscow_tz = pytz.timezone("Europe/Moscow")
notification_lock = asyncio.Lock()


@dataclass
class NotificationContext:
    bot: Bot
    session: AsyncSession
    current_time: int
    preload_data: Optional[dict] = None
    bulk_updates: Optional[dict] = None

    def get_balance(self, tg_id: int) -> float:
        if self.preload_data and tg_id in self.preload_data.get("balances_cache", {}):
            return self.preload_data["balances_cache"][tg_id]
        return 0.0

    def get_tariff(self, tariff_id: int) -> Optional[dict]:
        if self.preload_data and tariff_id in self.preload_data.get("tariffs_cache", {}):
            return self.preload_data["tariffs_cache"][tariff_id]
        return None


async def preload_notification_data(session: AsyncSession) -> dict[str, Any]:
    stmt = (
        select(
            Key,
            Tariff,
            User.balance.label("user_balance"),
        )
        .outerjoin(Tariff, Key.tariff_id == Tariff.id)
        .outerjoin(User, Key.tg_id == User.tg_id)
        .where(Key.is_frozen.is_(False))
    )

    result = await session.execute(stmt)
    rows = result.all()

    keys_data = {}
    tariffs_cache = {}
    balances_cache = {}

    for row in rows:
        key = row[0]
        tariff = row[1]
        balance = row[2] or 0.0

        key_dict = {
            "key": key,
            "tariff": dict(tariff.__dict__) if tariff else None,
            "balance": float(balance),
        }
        keys_data[key.client_id] = key_dict

        if tariff and tariff.id not in tariffs_cache:
            tariffs_cache[tariff.id] = dict(tariff.__dict__)

        balances_cache[key.tg_id] = float(balance)

    return {
        "keys_data": keys_data,
        "tariffs_cache": tariffs_cache,
        "balances_cache": balances_cache,
    }


async def execute_bulk_updates(session: AsyncSession, bulk_updates: dict[str, Any]) -> None:
    try:
        if bulk_updates["balance_changes"]:
            for tg_id, balance_change in bulk_updates["balance_changes"].items():
                await session.execute(
                    text("UPDATE users SET balance = balance + :change WHERE tg_id = :tg_id"),
                    {"change": balance_change, "tg_id": tg_id},
                )
            logger.info(f"Bulk: обновлено {len(bulk_updates['balance_changes'])} балансов")

        if bulk_updates["key_expiry_updates"]:
            for client_id, new_expiry in bulk_updates["key_expiry_updates"]:
                await session.execute(update(Key).where(Key.client_id == client_id).values(expiry_time=new_expiry))
            logger.info(f"Bulk: обновлено {len(bulk_updates['key_expiry_updates'])} сроков действия ключей")

        if bulk_updates["key_tariff_updates"]:
            for client_id, new_tariff_id in bulk_updates["key_tariff_updates"]:
                await session.execute(update(Key).where(Key.client_id == client_id).values(tariff_id=new_tariff_id))
            logger.info(f"Bulk: обновлено {len(bulk_updates['key_tariff_updates'])} тарифов ключей")

        for tg_id, notification_type in bulk_updates["notifications_to_add"]:
            await add_notification(session, tg_id, notification_type)

        for tg_id, notification_type in bulk_updates["notifications_to_delete"]:
            await delete_notification(session, tg_id, notification_type)

        if bulk_updates["notifications_to_add"] or bulk_updates["notifications_to_delete"]:
            logger.info(
                f"Bulk: обработано {len(bulk_updates['notifications_to_add'])} добавлений "
                f"и {len(bulk_updates['notifications_to_delete'])} удалений уведомлений"
            )

        await session.commit()

    except Exception as error:
        logger.error(f"Ошибка в bulk-обновлениях: {error}")
        await session.rollback()
        raise


async def send_expiry_warning(ctx: NotificationContext, key, hours_left: int, photo: str) -> bool:
    tg_id = key.tg_id
    email = key.email or ""

    expiry_data = await prepare_key_expiry_data(key, ctx.session, ctx.current_time)

    message_text = KEY_EXPIRY.format(
        email=email,
        hours_left_formatted=expiry_data["hours_left_formatted"],
        formatted_expiry_date=expiry_data["formatted_expiry_date"],
        tariff_name=expiry_data["tariff_name"],
        tariff_details=expiry_data["tariff_details"],
    )

    keyboard = build_notification_kb(email)
    return await send_notification(ctx.bot, tg_id, photo, message_text, keyboard)


async def send_cannot_renew(ctx: NotificationContext, key, photo: str) -> bool:
    tg_id = key.tg_id
    email = key.email or ""

    expiry_data = await prepare_key_expiry_data(key, ctx.session, ctx.current_time)

    message_text = KEY_CANNOT_RENEW_CURRENT.format(
        email=email,
        hours_left_formatted=expiry_data["hours_left_formatted"],
        formatted_expiry_date=expiry_data["formatted_expiry_date"],
        tariff_name=expiry_data["tariff_name"],
        tariff_details=expiry_data["tariff_details"],
    )

    keyboard = build_change_tariff_kb(email)
    return await send_notification(ctx.bot, tg_id, photo, message_text, keyboard)


async def send_expired_notification(ctx: NotificationContext, key, delay_minutes: int) -> bool:
    tg_id = key.tg_id
    email = key.email or ""

    if delay_minutes > 0:
        hours = delay_minutes // 60
        minutes = delay_minutes % 60
        if hours > 0 and minutes > 0:
            time_formatted = f"{format_hours(hours)} и {format_minutes(minutes)}"
        elif hours > 0:
            time_formatted = format_hours(hours)
        else:
            time_formatted = format_minutes(minutes)
        message_text = KEY_EXPIRED_DELAY_MSG.format(email=email, time_formatted=time_formatted)
    else:
        message_text = KEY_EXPIRED_NO_DELAY_MSG.format(email=email)

    keyboard = build_notification_kb(email)
    return await send_notification(ctx.bot, tg_id, "notify_expired.jpg", message_text, keyboard)


async def send_deleted_notification(ctx: NotificationContext, key) -> bool:
    tg_id = key.tg_id
    email = key.email or ""

    message_text = KEY_DELETED_MSG.format(email=email)
    keyboard = build_notification_expired_kb()
    return await send_notification(ctx.bot, tg_id, "notify_expired.jpg", message_text, keyboard)


async def send_renewed_notification(ctx: NotificationContext, key, tariff: dict, new_expiry_time: int) -> bool:
    tg_id = key.tg_id
    email = key.email or ""

    selected_device_limit = getattr(key, "selected_device_limit", None)
    selected_traffic_limit = getattr(key, "selected_traffic_limit", None)
    selected_traffic_gb = int(selected_traffic_limit) if selected_traffic_limit is not None else None

    device_limit_effective, traffic_limit_bytes_effective = await get_effective_limits_for_key(
        session=ctx.session,
        tariff_id=int(tariff["id"]),
        selected_device_limit=int(selected_device_limit) if selected_device_limit is not None else None,
        selected_traffic_gb=selected_traffic_gb,
    )
    traffic_limit_gb = int(traffic_limit_bytes_effective / GB) if traffic_limit_bytes_effective else 0

    formatted_expiry_date = datetime.fromtimestamp(new_expiry_time / 1000, tz=moscow_tz).strftime("%d %B %Y, %H:%M")
    formatted_expiry_date = formatted_expiry_date.replace(
        datetime.fromtimestamp(new_expiry_time / 1000, tz=moscow_tz).strftime("%B"),
        get_russian_month(datetime.fromtimestamp(new_expiry_time / 1000, tz=moscow_tz)),
    )

    message_text = get_renewal_message(
        tariff_name=tariff["name"],
        traffic_limit=traffic_limit_gb,
        device_limit=device_limit_effective,
        expiry_date=formatted_expiry_date,
        subgroup_title=tariff.get("subgroup_title", ""),
    )

    keyboard = build_notification_expired_kb()
    result = await send_notification(ctx.bot, tg_id, "notify_expired.jpg", message_text, keyboard)

    if result:
        logger.info(f"✅ Уведомление о продлении подписки {email} отправлено пользователю {tg_id}.")
    else:
        logger.warning(f"📢 Не удалось отправить уведомление о продлении подписки {email} пользователю {tg_id}.")

    return result


async def try_auto_renew(ctx: NotificationContext, key) -> tuple[bool, Optional[dict], Optional[int]]:
    tg_id = key.tg_id
    email = key.email or ""
    renew_notification_id = f"{email}_renew"

    can_renew = await check_notification_time(ctx.session, tg_id, renew_notification_id, hours=24)
    if not can_renew:
        logger.debug(f"⏳ Подписка {email} уже продлевалась в течение последних 24 часов.")
        return False, None, None

    if ctx.preload_data and tg_id in ctx.preload_data.get("balances_cache", {}):
        balance = ctx.preload_data["balances_cache"][tg_id]
    else:
        balance = await get_balance(ctx.session, tg_id)

    server_id = key.server_id
    tariff_id = key.tariff_id

    tariffs = await get_tariffs_for_cluster(ctx.session, server_id)
    if not tariffs:
        logger.warning(f"⛔ Нет доступных тарифов для продления подписки {email}")
        return False, None, None

    current_tariff = None
    if tariff_id:
        current_tariff = ctx.get_tariff(tariff_id)
        if not current_tariff and await check_tariff_exists(ctx.session, tariff_id):
            current_tariff = await get_tariff_by_id(ctx.session, tariff_id)

    if not current_tariff:
        return False, None, None

    forbidden_groups = ["discounts", "discounts_max", "gifts", "trial"]
    try:
        hook_results = await run_hooks("renewal_forbidden_groups", chat_id=tg_id, admin=False, session=ctx.session)
        for hook_result in hook_results:
            additional_groups = hook_result.get("additional_groups", [])
            forbidden_groups.extend(additional_groups)
    except Exception as error:
        logger.warning(f"[AUTO_RENEW] Ошибка при получении дополнительных групп: {error}")

    if current_tariff["group_code"] in forbidden_groups:
        return False, None, None

    renewal_cost = await resolve_price_to_charge(
        ctx.session,
        {
            "tariff_id": current_tariff.get("id"),
            "selected_device_limit": getattr(key, "selected_device_limit", None),
            "selected_traffic_limit": getattr(key, "selected_traffic_limit", None),
            "selected_price_rub": getattr(key, "selected_price_rub", None),
        },
    )

    if renewal_cost is None or balance < renewal_cost:
        return False, None, None

    client_id = key.client_id
    current_expiry = key.expiry_time
    duration_days = current_tariff["duration_days"]

    selected_device_limit = getattr(key, "selected_device_limit", None)
    selected_traffic_limit = getattr(key, "selected_traffic_limit", None)
    selected_traffic_gb = int(selected_traffic_limit) if selected_traffic_limit is not None else None

    device_limit_effective, traffic_limit_bytes_effective = await get_effective_limits_for_key(
        session=ctx.session,
        tariff_id=int(current_tariff["id"]),
        selected_device_limit=int(selected_device_limit) if selected_device_limit is not None else None,
        selected_traffic_gb=selected_traffic_gb,
    )
    traffic_limit_gb = int(traffic_limit_bytes_effective / GB) if traffic_limit_bytes_effective else 0

    new_expiry_time = (
        current_expiry
        if current_expiry > datetime.utcnow().timestamp() * 1000
        else datetime.utcnow().timestamp() * 1000
    ) + duration_days * 24 * 60 * 60 * 1000

    logger.info(
        f"Продление подписки {email} на {duration_days} дней для пользователя {tg_id}. "
        f"Баланс: {balance}, списываем: {renewal_cost}"
    )

    key_subgroup = current_tariff.get("subgroup_title")

    await renew_key_in_cluster(
        cluster_id=server_id,
        email=email,
        client_id=client_id,
        new_expiry_time=int(new_expiry_time),
        total_gb=traffic_limit_gb,
        hwid_device_limit=device_limit_effective,
        session=ctx.session,
        target_subgroup=key_subgroup,
        old_subgroup=key_subgroup,
        plan=current_tariff["id"],
    )

    new_tariff_device_limit = current_tariff.get("device_limit")
    new_tariff_traffic_limit = current_tariff.get("traffic_limit")
    reset_values = {
        "selected_device_limit": new_tariff_device_limit,
        "current_device_limit": new_tariff_device_limit,
        "selected_traffic_limit": new_tariff_traffic_limit,
        "current_traffic_limit": new_tariff_traffic_limit,
        "selected_price_rub": int(current_tariff["price_rub"]) if current_tariff.get("price_rub") is not None else None,
    }
    await ctx.session.execute(update(Key).where(Key.client_id == client_id).values(**reset_values))

    if ctx.bulk_updates is not None:
        if tg_id in ctx.bulk_updates["balance_changes"]:
            ctx.bulk_updates["balance_changes"][tg_id] -= renewal_cost
        else:
            ctx.bulk_updates["balance_changes"][tg_id] = -renewal_cost

        ctx.bulk_updates["key_expiry_updates"].append((client_id, int(new_expiry_time)))
        ctx.bulk_updates["key_tariff_updates"].append((client_id, current_tariff["id"]))
        ctx.bulk_updates["notifications_to_add"].append((tg_id, renew_notification_id))
    else:
        await update_balance(ctx.session, tg_id, -renewal_cost)
        await update_key_expiry(ctx.session, client_id, int(new_expiry_time))
        await update_key_tariff(ctx.session, client_id, current_tariff["id"])
        await add_notification(ctx.session, tg_id, renew_notification_id)

    return True, current_tariff, int(new_expiry_time)


async def notify_expiring_keys(
    ctx: NotificationContext,
    keys: list,
    min_hours: int,
    max_hours: int,
    notify_type: str,
    photo: str,
    notify_renew_enabled: bool,
):
    if min_hours > 0:
        logger.info(f"Начало проверки подписок, истекающих через {min_hours}-{max_hours} часов.")
    else:
        logger.info(f"Начало проверки подписок, истекающих через {max_hours} часов.")

    min_threshold = int((datetime.now(moscow_tz) + timedelta(hours=min_hours)).timestamp() * 1000)
    max_threshold = int((datetime.now(moscow_tz) + timedelta(hours=max_hours)).timestamp() * 1000)
    expiring_keys = [key for key in keys if key.expiry_time and min_threshold < key.expiry_time <= max_threshold]

    if min_hours > 0:
        logger.info(f"Найдено {len(expiring_keys)} подписок, истекающих через {min_hours}-{max_hours} часов.")
    else:
        logger.info(f"Найдено {len(expiring_keys)} подписок, истекающих через {max_hours} часов.")

    tg_ids = [key.tg_id for key in expiring_keys]
    emails = [key.email or "" for key in expiring_keys]
    allowed = await check_notifications_bulk(ctx.session, notify_type, max_hours, tg_ids=tg_ids, emails=emails)
    allowed_set = {(user["tg_id"], user["email"]) for user in allowed}

    messages = []

    for key in expiring_keys:
        tg_id = key.tg_id
        email = key.email or ""

        if (tg_id, email) not in allowed_set:
            continue

        notification_id = f"{email}_{notify_type}"

        can_notify = await check_notification_time(ctx.session, tg_id, notification_id, hours=max_hours)
        if not can_notify:
            continue

        if notify_renew_enabled:
            try:
                renewed, tariff, new_expiry = await try_auto_renew(ctx, key)

                if renewed and tariff and new_expiry:
                    await send_renewed_notification(ctx, key, tariff, new_expiry)
                    await add_notification(ctx.session, tg_id, notification_id)
                else:
                    await send_cannot_renew(ctx, key, photo)
                    await add_notification(ctx.session, tg_id, notification_id)

            except Exception as error:
                logger.error(f"Ошибка авто-продления/уведомления для пользователя {tg_id}: {error}")
        else:
            expiry_data = await prepare_key_expiry_data(key, ctx.session, ctx.current_time)
            notification_text = KEY_EXPIRY.format(
                email=email,
                hours_left_formatted=expiry_data["hours_left_formatted"],
                formatted_expiry_date=expiry_data["formatted_expiry_date"],
                tariff_name=expiry_data["tariff_name"],
                tariff_details=expiry_data["tariff_details"],
            )
            keyboard = build_notification_kb(email)
            messages.append({
                "tg_id": tg_id,
                "text": notification_text,
                "photo": photo,
                "keyboard": keyboard,
                "notification_id": notification_id,
                "email": email,
            })

    if messages:
        results = await send_messages_with_limit(ctx.bot, messages, session=ctx.session)
        sent_count = 0
        for msg, result in zip(messages, results, strict=False):
            await add_notification(ctx.session, msg["tg_id"], msg["notification_id"])
            if result:
                sent_count += 1
                logger.info(
                    f"Отправлено уведомление об истекающей подписке {msg['email']} пользователю {msg['tg_id']}."
                )
        logger.info(f"Отправлено {sent_count} уведомлений типа {notify_type}.")

    logger.info(f"Обработка уведомлений {notify_type} завершена.")
    await asyncio.sleep(1)


async def handle_expired_keys(ctx: NotificationContext, keys: list):
    logger.info("Начало обработки истекших ключей.")

    expired_keys = [key for key in keys if key.expiry_time and key.expiry_time < ctx.current_time]
    logger.info(f"Найдено {len(expired_keys)} истекших ключей.")

    tg_ids = [key.tg_id for key in expired_keys]
    emails = [key.email or "" for key in expired_keys]
    users = await check_notifications_bulk(ctx.session, "key_expired", 0, tg_ids=tg_ids, emails=emails)
    users_set = {(user["tg_id"], user["email"]) for user in users}

    notify_renew_expired_enabled = bool(NOTIFICATIONS_CONFIG.get("RENEW_EXPIRED_ENABLED", NOTIFY_RENEW_EXPIRED))
    notify_delete_key_enabled = bool(NOTIFICATIONS_CONFIG.get("DELETE_KEY_ENABLED", NOTIFY_DELETE_KEY))
    delete_key_delay_minutes = int(NOTIFICATIONS_CONFIG.get("DELETE_KEY_DELAY_MINUTES", NOTIFY_DELETE_DELAY))

    for key in expired_keys:
        tg_id = key.tg_id
        email = key.email or ""
        client_id = key.client_id
        server_id = key.server_id
        notification_id = f"{email}_key_expired"

        last_notification_time = await get_last_notification_time(ctx.session, tg_id, notification_id)

        if notify_renew_expired_enabled:
            try:
                renewed, tariff, new_expiry = await try_auto_renew(ctx, key)

                if renewed and tariff and new_expiry:
                    await send_renewed_notification(ctx, key, tariff, new_expiry)
                    if ctx.bulk_updates:
                        ctx.bulk_updates["notifications_to_delete"].append((tg_id, notification_id))
                    else:
                        await delete_notification(ctx.session, tg_id, notification_id)
                    continue

            except Exception as error:
                logger.error(f"Ошибка авто-продления для пользователя {tg_id}: {error}")
                continue

        if notify_delete_key_enabled:
            should_delete = False

            if delete_key_delay_minutes == 0:
                should_delete = True
            elif last_notification_time is not None:
                minutes_passed = (ctx.current_time - last_notification_time) / (1000 * 60)
                should_delete = minutes_passed >= delete_key_delay_minutes
                logger.info(f"Прошло минут={minutes_passed:.2f} DELETE_KEY_DELAY_MINUTES={delete_key_delay_minutes}")

            if should_delete:
                try:
                    await delete_key_from_cluster(server_id, email, client_id, ctx.session)
                    await delete_key(ctx.session, client_id)
                    logger.info(f"🗑 Ключ {client_id} для пользователя {tg_id} успешно удалён.")

                    await send_deleted_notification(ctx, key)
                except Exception as error:
                    logger.error(f"Ошибка удаления ключа {client_id} для пользователя {tg_id}: {error}")
                continue

        if last_notification_time is None and (tg_id, email) in users_set:
            await send_expired_notification(ctx, key, delete_key_delay_minutes)
            await add_notification(ctx.session, tg_id, notification_id)

    logger.info("Обработка истекших ключей завершена.")
    await asyncio.sleep(1)


async def periodic_notifications(bot: Bot, *, sessionmaker: async_sessionmaker):
    while True:
        notification_interval = int(NOTIFICATIONS_CONFIG.get("BASE_NOTIFICATION_MINUTE", NOTIFICATION_TIME))

        if notification_lock.locked():
            logger.warning("Уведомления уже выполняются. Пропуск...")
            await asyncio.sleep(notification_interval)
            continue

        async with notification_lock:
            try:
                async with sessionmaker() as session:
                    logger.info("Запуск обработки уведомлений")

                    current_time = int(datetime.now(moscow_tz).timestamp() * 1000)
                    start_time = datetime.now()

                    try:
                        preload_data = await preload_notification_data(session)
                        keys_data = preload_data["keys_data"]
                        keys = [data["key"] for data in keys_data.values()]
                        preload_time = (datetime.now() - start_time).total_seconds()
                        logger.info(
                            f"Предзагружено данных: {len(keys)} ключей, "
                            f"{len(preload_data['tariffs_cache'])} тарифов за {preload_time:.2f}s"
                        )

                        bulk_updates = {
                            "balance_changes": {},
                            "key_expiry_updates": [],
                            "key_tariff_updates": [],
                            "notifications_to_add": [],
                            "notifications_to_delete": [],
                        }

                    except Exception as error:
                        logger.error(f"Ошибка при предварительной загрузке данных: {error}")
                        try:
                            keys = await get_all_keys(session=session)
                            keys = [k for k in keys if not k.is_frozen]
                            preload_data = None
                            bulk_updates = None
                            preload_time = (datetime.now() - start_time).total_seconds()
                            logger.info(f"Fallback: получено {len(keys)} ключей за {preload_time:.2f}s")
                        except Exception as fallback_error:
                            logger.error(f"Ошибка fallback получения ключей: {fallback_error}")
                            keys = []
                            preload_data = None
                            bulk_updates = None

                    ctx = NotificationContext(
                        bot=bot,
                        session=session,
                        current_time=current_time,
                        preload_data=preload_data,
                        bulk_updates=bulk_updates,
                    )

                    trial_time_disable = bool(MODES_CONFIG.get("TRIAL_TIME_DISABLED", TRIAL_TIME_DISABLE))
                    if not trial_time_disable:
                        try:
                            await notify_inactive_trial_users(bot, session)
                        except Exception as error:
                            logger.error(f"Ошибка в notify_inactive_trial_users: {error}")

                    notify_24_enabled = bool(NOTIFICATIONS_CONFIG.get("EXPIRY_24H_ENABLED", NOTIFY_24H_ENABLED))
                    notify_24_hours = int(NOTIFICATIONS_CONFIG.get("EXPIRY_24H_BEFORE_HOURS", NOTIFY_24H_HOURS))
                    notify_10_enabled = bool(NOTIFICATIONS_CONFIG.get("EXPIRY_10H_ENABLED", NOTIFY_10H_ENABLED))
                    notify_10_hours = int(NOTIFICATIONS_CONFIG.get("EXPIRY_10H_BEFORE_HOURS", NOTIFY_10H_HOURS))
                    notify_renew_enabled = bool(NOTIFICATIONS_CONFIG.get("RENEW_ENABLED", NOTIFY_RENEW))
                    inactive_traffic_enabled = bool(
                        NOTIFICATIONS_CONFIG.get("INACTIVE_TRAFFIC_ENABLED", NOTIFY_INACTIVE_TRAFFIC)
                    )
                    notify_hot_leads_enabled = bool(NOTIFICATIONS_CONFIG.get("HOT_LEADS_ENABLED", NOTIFY_HOT_LEADS))

                    if notify_24_enabled:
                        try:
                            await notify_expiring_keys(
                                ctx,
                                keys,
                                min_hours=notify_10_hours if notify_10_enabled else 0,
                                max_hours=notify_24_hours,
                                notify_type="key_24h",
                                photo="notify_24h.jpg",
                                notify_renew_enabled=notify_renew_enabled,
                            )
                        except Exception as error:
                            logger.error(f"Ошибка в notify_expiring_keys (24h): {error}")

                    if notify_10_enabled:
                        try:
                            await notify_expiring_keys(
                                ctx,
                                keys,
                                min_hours=0,
                                max_hours=notify_10_hours,
                                notify_type="key_10h",
                                photo="notify_10h.jpg",
                                notify_renew_enabled=notify_renew_enabled,
                            )
                        except Exception as error:
                            logger.error(f"Ошибка в notify_expiring_keys (10h): {error}")

                    try:
                        await handle_expired_keys(ctx, keys)
                    except Exception as error:
                        logger.error(f"Ошибка в handle_expired_keys: {error}")

                    if inactive_traffic_enabled:
                        try:
                            await notify_users_no_traffic(bot, session, current_time, keys)
                        except Exception as error:
                            logger.error(f"Ошибка в notify_users_no_traffic: {error}")

                    try:
                        await run_hooks("periodic_notifications", bot=bot, session=session, keys=keys)
                    except Exception as error:
                        logger.error(f"Ошибка в хуках periodic_notifications: {error}")

                    if notify_hot_leads_enabled:
                        try:
                            await notify_hot_leads(bot, session)
                        except Exception as error:
                            logger.error(f"Ошибка в notify_hot_leads: {error}")

                    if bulk_updates:
                        bulk_start = datetime.now()
                        await execute_bulk_updates(session, bulk_updates)
                        bulk_time = (datetime.now() - bulk_start).total_seconds()
                        total_renewals = len(bulk_updates["balance_changes"])
                        total_key_updates = len(bulk_updates["key_expiry_updates"]) + len(
                            bulk_updates["key_tariff_updates"]
                        )
                        total_notification_updates = len(bulk_updates["notifications_to_add"]) + len(
                            bulk_updates["notifications_to_delete"]
                        )
                        logger.info(
                            f"Bulk-операции выполнены за {bulk_time:.2f}s. Обработано: {total_renewals} продлений, {total_key_updates} ключей, {total_notification_updates} уведомлений"
                        )

                    total_time = (datetime.now() - start_time).total_seconds()
                    logger.info(f"Уведомления завершены за {total_time:.2f}s")

            except Exception as error:
                logger.error(f"Ошибка в periodic_notifications: {error}")

        await asyncio.sleep(notification_interval)
