import asyncio
import time
import logging
import os
import platform
import re
import json
import subprocess
import sys
import traceback

from aiogram import Router
from aiogram.types import CallbackQuery
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from bs4 import BeautifulSoup
from sqlalchemy.exc import IntegrityError


from app.database.crud import *
from app.database.crud_counter import *
from app.database.crud_counter import finalize_counters
from app.database.crud_static import add_sms_log, get_last_successful_sms_time
from config import (
    PARSING_PAUSE_BETWEEN_ADS_SEC_MAX,
    PARSING_PAUSE_BETWEEN_ADS_SEC_MIN,
    PARSING_PAUSE_BETWEEN_ITERATIONS_SEC_MAX,
    PARSING_PAUSE_BETWEEN_ITERATIONS_SEC_MIN,
    PARSING_PAUSE_ON_SKIP_SEC_MAX,
    PARSING_PAUSE_ON_SKIP_SEC_MIN,
    CHROME_RESTART_EVERY_ADS,
    PARSING_AFTER_CLICK_WAIT_SEC,
    PARSING_PAGE_WAIT_SEC,
    PARSING_PHONE_FIND_WAIT_SEC,
    PARSING_RECONNECT_WAIT_SEC,
    PARSING_SCROLL_WAIT_SEC,
    PARSING_REPUBLISHED_LAST,
    PARSING_SEARCH_PAGES,
    PARSING_SKIP_NEWAUTO,
    PARSING_STAGE_PAUSE_SEC,
    PARSING_START_SMS_TEST_ENABLED,
    PARSING_START_SMS_TEST_NUMBER,
    SMS_DAILY_LIMIT,
    SMS_SKIP_ALREADY_SENT,
    effective_sms_resend_after_days,
)
from spam_sms import send_sms
from test import (
    _normalize_region_ids,
    search_extended,
    search_extended_pages,
)


# Ошибки, после которых драйвер/вкладка уже мертвы — нужен немедленный рестарт
_FATAL_BROWSER_ERROR_MARKERS = (
    "tab crashed",
    "session deleted",
    "invalid session",
    "chrome not reachable",
    "disconnected",
    "cannot connect to chrome",
    "browser has closed",
    "no such window",
    "target window already closed",
    "failed to establish a new connection",
)


def _reap_zombie_children() -> int:
    """Собирает exit-статусы завершённых дочерних процессов (убирает <defunct>/zombie)."""
    reaped = 0
    while True:
        try:
            pid, _ = os.waitpid(-1, os.WNOHANG)
            if pid == 0:
                break
            reaped += 1
        except ChildProcessError:
            break
        except Exception:
            break
    return reaped


def _force_kill_chrome_processes() -> None:
    """Убивает зависшие Chrome/ChromeDriver и собирает zombie (только Linux)."""
    if platform.system() != "Linux":
        _reap_zombie_children()
        return

    # Не использовать голый паттерн «chrome» — pkill -f chrome слишком широкий.
    for args in (
        ["pkill", "-9", "-f", "chromedriver"],
        ["pkill", "-9", "-f", "/opt/google/chrome/chrome"],
        ["pkill", "-9", "-f", "google-chrome"],
        ["pkill", "-9", "-f", "chromium-browser"],
        ["pkill", "-9", "-f", "chromium"],
        ["killall", "-9", "chromedriver"],
        ["killall", "-9", "google-chrome"],
        ["killall", "-9", "chromium"],
    ):
        try:
            subprocess.run(args, timeout=5, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        except Exception:
            pass

    time.sleep(1)
    reaped = _reap_zombie_children()
    if reaped:
        logging.info("🧹 Собрано zombie-процессов: %s", reaped)


def _safe_quit_driver(driver) -> None:
    """Корректно закрывает WebDriver и подчищает живые/zombie процессы Chrome."""
    if not driver:
        _force_kill_chrome_processes()
        return
    try:
        driver.quit()
    except Exception as e:
        logging.debug("driver.quit() ошибка (игнорируем): %s", e)
    finally:
        _force_kill_chrome_processes()


def _pause_after_ad_outcome(sms_outcome: str | None, stop_event) -> None:
    """Пауза перед следующим объявлением с учётом результата SMS.

    Дубли / skip — короткая пауза (не жечь 1–2 мин впустую).
    Иначе — обычная пауза между объявлениями.
    """
    import random

    if stop_event.is_set():
        return

    if sms_outcome in ("skipped", "no_valid", "limited", "debug"):
        lo = PARSING_PAUSE_ON_SKIP_SEC_MIN
        hi = PARSING_PAUSE_ON_SKIP_SEC_MAX
        if hi <= 0:
            return
        pause_time = random.randint(max(0, lo), hi)
        logging.info(
            "😴 Быстрая пауза %s с (outcome=%s, дубль/без SMS)...",
            pause_time,
            sms_outcome,
        )
    else:
        pause_time = random.randint(
            PARSING_PAUSE_BETWEEN_ADS_SEC_MIN,
            PARSING_PAUSE_BETWEEN_ADS_SEC_MAX,
        )
        if pause_time >= 60:
            logging.info(
                "😴 Пауза %s мин перед следующим объявлением...",
                pause_time // 60,
            )
        else:
            logging.info(
                "😴 Пауза %s с перед следующим объявлением...",
                pause_time,
            )

    for _ in range(pause_time):
        if stop_event.is_set():
            break
        time.sleep(1)


def extract_car_info(ad: dict) -> dict:
    """Извлекает данные из объявления."""
    data = ad.get('component', {}).get('advertisementCard', {}).get('data', {})
    ad_id = data.get('id')
    name = data.get('title', {}).get('content')
    # Попытка извлечь год из названия
    year = None
    if name:
        match = re.search(r'\b(19|20)\d{2}\b', name)
        if match:
            year = match.group(0)
    price_usd = data.get('price', {}).get('USD')
    publish_time = data.get('publishTime')
    link = data.get('link')
    return {
        'id': ad_id,
        'name': name,
        'year': year,
        'price_usd': price_usd,
        'publish_time': publish_time,
        'link': link
    }


def _autoinfo_kwargs_from_info(info: dict, link: str, all_phones: list) -> dict:
    """Поля для add_autoinfo: безопасные типы (цена/год из API часто None)."""
    raw_year = info.get("year")
    try:
        year = int(raw_year) if raw_year is not None and str(raw_year).strip() != "" else 0
    except (TypeError, ValueError):
        year = 0
    pu = info.get("price_usd")
    try:
        usd_price = int(float(pu)) if pu is not None else 0
    except (TypeError, ValueError):
        usd_price = 0
    pt = info.get("publish_time")
    return {
        "ad_id": str(info.get("id") or ""),
        "name": (info.get("name") or "") or "",
        "year": year,
        "usd_price": usd_price,
        "publish_time": str(pt) if pt is not None else "",
        "all_phones": all_phones,
        "link": link,
        "spam": False,
    }


def _persist_autoinfo_after_sms_ok(info: dict, link: str, all_phones: list, _await) -> None:
    """В статистику парсинга пишем объявление только после успешной SMS."""
    try:
        kw = _autoinfo_kwargs_from_info(info, link, all_phones)
        if not kw.get("ad_id"):
            return
        _await(add_autoinfo(**kw))
        logging.info(
            "✅ Объявление %s сохранено в статистику (SMS ok)",
            info.get("id"),
        )
    except IntegrityError:
        _await(update_autoinfo_phones(str(info.get("id") or ""), all_phones))
        logging.info(
            "🔄 Объявление %s уже в статистике — телефоны обновлены (SMS ok)",
            info.get("id"),
        )
    except Exception as db_exc:
        logging.error(
            "⚠️ Ошибка сохранения в статистику %s: %s",
            info.get("id"),
            db_exc,
        )


def _format_time_ago(dt) -> str:
    """Форматирует datetime в строку '5 мин назад', '2 ч назад', '1 д назад'."""
    if dt is None:
        return "нет данных"
    from datetime import datetime

    now = datetime.utcnow()
    delta = now - dt
    total_seconds = int(delta.total_seconds())
    if total_seconds < 60:
        return f"{total_seconds} сек назад"
    minutes = total_seconds // 60
    if minutes < 60:
        return f"{minutes} мин назад"
    hours = minutes // 60
    if hours < 24:
        remaining_min = minutes % 60
        if remaining_min:
            return f"{hours} ч {remaining_min} мин назад"
        return f"{hours} ч назад"
    days = hours // 24
    remaining_hours = hours % 24
    if remaining_hours:
        return f"{days} д {remaining_hours} ч назад"
    return f"{days} д назад"


async def _gather(*aws):
    """asyncio.gather нельзя вызывать из worker-потока без loop — только внутри coro."""
    return await asyncio.gather(*aws)


def _refresh_daily_limit_flags(daily_limit_state: dict) -> None:
    """Сброс флагов лимита при смене календарного дня (по TZ бота)."""
    from app.schedule_utils import now_in_bot_tz

    today = now_in_bot_tz().date().isoformat()
    if daily_limit_state.get("date") != today:
        daily_limit_state.clear()
        daily_limit_state["date"] = today
        daily_limit_state["notified"] = False
        daily_limit_state["notified_80"] = False


def _maybe_notify_sms_limit(
    *,
    today_sent: int,
    daily_limit_state: dict,
    _notify_user,
) -> None:
    """Уведомления на 80% и 100% дневного лимита (один раз за день)."""
    if SMS_DAILY_LIMIT <= 0:
        return
    _refresh_daily_limit_flags(daily_limit_state)
    threshold_80 = int(SMS_DAILY_LIMIT * 0.8)
    if (
        today_sent >= threshold_80
        and today_sent < SMS_DAILY_LIMIT
        and not daily_limit_state.get("notified_80")
    ):
        daily_limit_state["notified_80"] = True
        _notify_user(
            f"⚠️ Дневной лимит SMS на 80%: {today_sent}/{SMS_DAILY_LIMIT}.",
            also_admin=True,
        )
    if today_sent >= SMS_DAILY_LIMIT and not daily_limit_state.get("notified"):
        daily_limit_state["notified"] = True
        _notify_user(
            f"⏸ Дневной лимит SMS достигнут: {today_sent}/{SMS_DAILY_LIMIT}.\n"
            "Новые SMS сегодня не отправляются. "
            "Лимит сбросится завтра (или измените SMS_DAILY_LIMIT в .env).",
            also_admin=True,
        )


def _try_ad_sms(
    *,
    info: dict,
    link: str,
    all_phones: list,
    debug_mode: bool,
    daily_limit_state: dict,
    _await,
    _notify_user,
    user_id: int | None = None,
    work_from: str | None = None,
    work_to: str | None = None,
    work_days=None,
) -> str:
    """
    Резерв + отправка SMS (первый валидный номер).
    Вне рабочих часов — постановка в очередь pending_sms.
    Возвращает: debug | skipped | sent | failed | limited | no_valid | deferred
    """
    from spam_sms import clean_phone_number, extract_clean_phones
    from app.database.crud_static import (
        count_today_sent_sms,
        is_phone_sent,
        mark_phone_sent,
        try_reserve_phone_for_sms,
    )
    from app.database.crud_pending_sms import enqueue_pending_sms
    from app.schedule_utils import is_within_window

    clean_phones = extract_clean_phones(all_phones)

    logging.info("📱 Очищенные номера для SMS: %s", clean_phones)

    if debug_mode:
        phones_show = clean_phones or [str(p) for p in all_phones]
        price = info.get("price_usd")
        year = info.get("year")
        _notify_user(
            "🧪 <b>DEBUG</b> — SMS нет, в БД не сохранено\n"
            f"📋 {info.get('name', 'Неизвестно')}\n"
            f"🆔 <code>{info.get('id')}</code>\n"
            f"📞 {', '.join(phones_show)}\n"
            f"💰 {price if price is not None else '—'} USD · "
            f"год {year if year is not None else '—'}\n"
            f"🔗 {link}"
        )
        logging.info(
            "🧪 DEBUG: объявление %s — телефоны %s (без БД / без SMS)",
            info.get("id"),
            phones_show,
        )
        return "debug"

    if not clean_phones:
        logging.warning(
            "⚠️ Не удалось очистить номера для объявления %s", info.get("id")
        )
        return "no_valid"

    first = clean_phones[0]
    logging.info("📱 Используем первый номер из списка: %s", [first])
    resend_days = effective_sms_resend_after_days()

    # Вне окна — в очередь, без reserve / TurboSMS
    in_window = is_within_window(work_from, work_to, work_days)
    if not in_window and user_id:
        if _await(is_phone_sent(first, resend_days)):
            logging.info(
                "📱 Номер %s уже получал SMS — вне окна не ставим в очередь (объявление %s)",
                first,
                info.get("id"),
            )
            return "skipped"
        queued = _await(
            enqueue_pending_sms(
                user_id=int(user_id),
                phone_number=first,
                ad_id=str(info.get("id")) if info.get("id") is not None else None,
                ad_name=info.get("name", "Неизвестно"),
                link=link,
            )
        )
        if queued is None:
            logging.info(
                "📱 Номер %s уже в pending — пропуск объявления %s",
                first,
                info.get("id"),
            )
            return "deferred"
        logging.info(
            "⏳ SMS отложена до рабочих часов: %s (объявление %s)",
            first,
            info.get("id"),
        )
        if not daily_limit_state.get("deferred_notified"):
            daily_limit_state["deferred_notified"] = True
            _notify_user(
                "⏳ SMS вне рабочих часов — ставим в очередь.\n"
                "Отправка начнётся в окне work_from–work_to (настройки расписания)."
            )
        return "deferred"

    reserved = _await(
        try_reserve_phone_for_sms(
            phone_number=first,
            ad_id=str(info.get("id")) if info.get("id") is not None else None,
            ad_name=info.get("name", "Неизвестно"),
            resend_after_days=resend_days,
        )
    )
    if not reserved:
        logging.info(
            "📱 Номер %s пропущен (уже был SMS / cooldown %s дн., skip_already=%s) — объявление %s",
            first,
            resend_days,
            SMS_SKIP_ALREADY_SENT,
            info.get("id"),
        )
        return "skipped"

    new_phones = [first]
    logging.info("📱 Новые номера для отправки SMS: %s", new_phones)

    if SMS_DAILY_LIMIT > 0:
        today_sent = _await(count_today_sent_sms())
        _maybe_notify_sms_limit(
            today_sent=today_sent,
            daily_limit_state=daily_limit_state,
            _notify_user=_notify_user,
        )
        if today_sent >= SMS_DAILY_LIMIT:
            logging.warning(
                "⏸ Дневной лимит SMS достигнут: %s/%s — пропускаем отправку",
                today_sent,
                SMS_DAILY_LIMIT,
            )
            _await(
                _gather(
                    *[
                        mark_phone_sent(
                            phone_number=phone,
                            ad_id=info.get("id"),
                            ad_name=info.get("name", "Неизвестно"),
                            is_successful=False,
                        )
                        for phone in new_phones
                    ]
                )
            )
            return "limited"

    try:
        sms_result = _await(send_sms(new_phones))
        if sms_result["success"]:
            logging.info(
                "✅ SMS успешно отправлены на %s номеров", sms_result["sent_count"]
            )
            try:
                _prev_sms_str = _format_time_ago(
                    _await(get_last_successful_sms_time())
                )
            except Exception:
                _prev_sms_str = "нет данных"
            _notify_user(
                f"✅ SMS отправлено!\n"
                f"📋 {info.get('name', 'Неизвестно')}\n"
                f"📞 {', '.join(new_phones)}\n"
                f"🔗 {link}\n"
                f"⏱ Предыдущее SMS: {_prev_sms_str}"
            )
            _await(
                _gather(
                    *[
                        mark_phone_sent(
                            phone_number=phone,
                            ad_id=info.get("id"),
                            ad_name=info.get("name", "Неизвестно"),
                            is_successful=True,
                        )
                        for phone in new_phones
                    ],
                    increment_global_counter(ok=True, step=sms_result["sent_count"]),
                    add_sms_log(
                        ad_name=info.get("name", "Неизвестно"),
                        link=link,
                        phones=new_phones,
                        is_sent=True,
                    ),
                )
            )
            if SMS_DAILY_LIMIT > 0:
                after = _await(count_today_sent_sms())
                _maybe_notify_sms_limit(
                    today_sent=after,
                    daily_limit_state=daily_limit_state,
                    _notify_user=_notify_user,
                )
            return "sent"

        logging.error("❌ Ошибка отправки SMS: %s", sms_result["error_message"])
        try:
            _prev_sms_str = _format_time_ago(_await(get_last_successful_sms_time()))
        except Exception:
            _prev_sms_str = "нет данных"
        _notify_user(
            f"❌ SMS не отправлено!\n"
            f"📋 {info.get('name', 'Неизвестно')}\n"
            f"📞 {', '.join(new_phones)}\n"
            f"Причина: {sms_result.get('error_message', 'неизвестно')}\n"
            f"⏱ Последнее успешное SMS: {_prev_sms_str}",
            also_admin=True,
        )
        _await(
            _gather(
                *[
                    mark_phone_sent(
                        phone_number=phone,
                        ad_id=info.get("id"),
                        ad_name=info.get("name", "Неизвестно"),
                        is_successful=False,
                    )
                    for phone in new_phones
                ],
                increment_global_counter(ok=False, step=len(new_phones)),
                add_sms_log(
                    ad_name=info.get("name", "Неизвестно"),
                    link=link,
                    phones=new_phones,
                    is_sent=False,
                ),
            )
        )
        return "failed"
    except Exception as sms_exception:
        logging.error("❌ Исключение при отправке SMS: %s", sms_exception)
        _notify_user(
            f"❌ Сбой при отправке SMS: {sms_exception}",
            also_admin=True,
        )
        _await(
            _gather(
                *[
                    mark_phone_sent(
                        phone_number=phone,
                        ad_id=info.get("id"),
                        ad_name=info.get("name", "Неизвестно"),
                        is_successful=False,
                    )
                    for phone in new_phones
                ],
                increment_global_counter(ok=False, step=len(new_phones)),
                add_sms_log(
                    ad_name=info.get("name", "Неизвестно"),
                    link=link,
                    phones=new_phones,
                    is_sent=False,
                ),
            )
        )
        return "failed"


def do_parsing(
    filter_settings,
    stop_event,
    bot=None,
    user_id=None,
    main_loop=None,
    debug_mode=False,
):
    """
    Синхронная функция парсинга, которая запускается в отдельном потоке.
    Здесь выполняется блокирующий код (Selenium, time.sleep и т.д.).
    Все ошибки внутри обрабатываются и логируются, чтобы не прерывать работу бота.

    debug_mode=True — тот же парсинг по фильтру, но без SMS: каждое найденное
    объявление с телефоном пишется в Telegram (тест без расходов).
    """
    if debug_mode:
        logging.info("🧪 DEBUG-парсинг запущен (SMS отключены, отчёт в Telegram)")
    else:
        logging.info("Фоновый парсинг запущен")
    logging.info(
        "🔄 Режим: бесконечный цикл; пауза между объявлениями %s–%s с; между итерациями %s–%s с",
        PARSING_PAUSE_BETWEEN_ADS_SEC_MIN,
        PARSING_PAUSE_BETWEEN_ADS_SEC_MAX,
        PARSING_PAUSE_BETWEEN_ITERATIONS_SEC_MIN,
        PARSING_PAUSE_BETWEEN_ITERATIONS_SEC_MAX,
    )
    logging.info(
        "📄 Страниц поиска: %s; republished_last=%s; skip newauto: %s; "
        "SMS_SKIP_ALREADY_SENT=%s (cooldown=%s дн.)",
        PARSING_SEARCH_PAGES,
        PARSING_REPUBLISHED_LAST if PARSING_REPUBLISHED_LAST > 0 else "off",
        "on" if PARSING_SKIP_NEWAUTO else "off",
        SMS_SKIP_ALREADY_SENT,
        effective_sms_resend_after_days(),
    )
    logging.info(
        "🌐 Chrome restart every %s ads; waits: page=%ss click=%ss phone=%ss reconnect=%ss",
        CHROME_RESTART_EVERY_ADS or "off",
        PARSING_PAGE_WAIT_SEC,
        PARSING_AFTER_CLICK_WAIT_SEC,
        PARSING_PHONE_FIND_WAIT_SEC,
        PARSING_RECONNECT_WAIT_SEC,
    )
    logging.info("🛑 Парсинг остановится только при нажатии кнопки остановки пользователем")

    def _notify_user(text: str, *, also_admin: bool = False):
        """Отправляет уведомление пользователю в Telegram (fire-and-forget)."""
        if not (bot and main_loop and not main_loop.is_closed()):
            return
        try:
            from app.notify import notify_user_and_maybe_admin

            asyncio.run_coroutine_threadsafe(
                notify_user_and_maybe_admin(user_id, text, also_admin=also_admin),
                main_loop,
            )
        except Exception as _ne:
            logging.debug("Не удалось отправить уведомление: %s", _ne)

    def _sleep(seconds: int):
        """Прерываемый sleep — проверяет stop_event каждую секунду."""
        for _ in range(seconds):
            if stop_event.is_set():
                return
            time.sleep(1)

    def _await(coro, timeout: float = 180.0):
        """Async из потока парсинга — только через main_loop бота."""
        if main_loop is None or main_loop.is_closed():
            raise RuntimeError("main_loop недоступен для парсинга")
        return asyncio.run_coroutine_threadsafe(coro, main_loop).result(timeout=timeout)

    def _progress(msg: str) -> None:
        # stderr не использует logging.Lock — не дедлится с SQLAlchemy echo
        try:
            print(msg, file=sys.stderr, flush=True)
        except Exception:
            pass
        try:
            logging.info("%s", msg)
        except Exception:
            pass

    # gosu(uid=1000) часто обнуляет HOME; /app owned by autoria → профиль в temp
    _chrome_home = "/app/temp/chrome-home"
    os.environ["HOME"] = _chrome_home
    os.environ["XDG_CONFIG_HOME"] = f"{_chrome_home}/.config"
    os.environ["XDG_CACHE_HOME"] = f"{_chrome_home}/.cache"
    try:
        os.makedirs(f"{_chrome_home}/.local", exist_ok=True)
        os.makedirs(os.environ["XDG_CONFIG_HOME"], exist_ok=True)
        os.makedirs(os.environ["XDG_CACHE_HOME"], exist_ok=True)
        _progress(f"HOME для Chrome: {_chrome_home}")
    except Exception as e:
        _progress(f"Не удалось создать HOME-каталоги: {e}")

    try:
        # filter_settings уже dict; пустой region_id [] → все регионы
        typeavto = filter_settings.get("typeavto") or "1"
        region_id = filter_settings.get("region_id")
        if region_id is None or region_id == [] or region_id == "":
            region_id = 0
        year_first = filter_settings.get("year_first") or "0"
        year_second = filter_settings.get("year_second") or "0"
        price_first = filter_settings.get("price_first") or "0"
        price_second = filter_settings.get("price_second") or "0"
        region_param = _normalize_region_ids(region_id)
        _progress(
            f"Фильтр: type={typeavto} regions={region_param} "
            f"years={year_first}-{year_second} price={price_first}-{price_second}"
        )
    except Exception as e:
        _progress(f"Сбой чтения фильтра: {e}")
        logging.error(traceback.format_exc())
        return

    if debug_mode:
        _notify_user(
            "🧪 <b>DEBUG-парсинг запущен</b>\n"
            "Поиск как в обычном режиме, но:\n"
            "• SMS <b>не отправляются</b>\n"
            "• объявления и номера <b>не пишутся в БД</b> "
            "(потом на них можно слать SMS обычным Запуском)\n"
            "Остановка — кнопка «Стоп»."
        )

    pause_ads_min = PARSING_PAUSE_BETWEEN_ADS_SEC_MIN
    pause_ads_max = PARSING_PAUSE_BETWEEN_ADS_SEC_MAX
    pause_iter_min = PARSING_PAUSE_BETWEEN_ITERATIONS_SEC_MIN
    pause_iter_max = PARSING_PAUSE_BETWEEN_ITERATIONS_SEC_MAX

    try:
        _progress("⏳ Пробный запрос к API RIA (search_extended)...")
        search_result = _await(
            search_extended(
                year_from=str(year_first),
                year_to=str(year_second),
                category=int(typeavto) if str(typeavto).isdigit() else 1,
                region=region_param,
                price_first=float(price_first),
                price_second=float(price_second),
                bodystyle=[],
                republished_last=PARSING_REPUBLISHED_LAST,
            ),
            timeout=60.0,
        )
        _progress(f"✅ Результат API получен. Keys: {list(search_result.keys())}")
    except Exception as ex:
        _progress(f"❌ Ошибка при вызове search_extended: {ex}")
        logging.error(traceback.format_exc())
        _notify_user(f"❌ DEBUG/парсинг: ошибка API: {ex}", also_admin=True)
        return

    items_section = None
    templates = search_result.get("templates", [])
    logging.info("🔍 Поиск шаблона 'items' среди %s шаблонов...", len(templates))
    for template in templates:
        if template.get("id") == "items":
            items_section = template
            break

    if not items_section:
        logging.info("⚠️ Объявления не найдены (блок items отсутствует)")
        _notify_user(
            "⚠️ API не вернул объявления по текущему фильтру "
            "(блок items пуст). Проверьте фильтр / republished_last."
        )
        return

    probe_count = len(items_section.get("templates") or [])
    logging.info("✅ Блок объявлений найден, в первой странице: %s", probe_count)
    _notify_user(
        f"{'🧪 DEBUG: ' if debug_mode else ''}"
        f"API ок — на 1-й странице ~<b>{probe_count}</b> объявлений.\n"
        "Дальше запускаю Chrome и разбираю телефоны…"
    )

    # Диагностика системы перед запуском Chrome (Linux)
    import platform
    import subprocess

    logging.info("🔍 Диагностика системы перед запуском Chrome...")
    
    if platform.system() == 'Linux':
        try:
            import psutil
            # Проверка памяти
            mem = psutil.virtual_memory()
            mem_available_gb = mem.available / (1024**3)
            mem_total_gb = mem.total / (1024**3)
            logging.info(f"💾 Доступная память: {mem_available_gb:.2f} GB из {mem_total_gb:.2f} GB")
            if mem.available < 500 * 1024 * 1024:  # Меньше 500 MB
                logging.warning("⚠️ ВНИМАНИЕ: Мало свободной памяти! Chrome может падать!")
            
            # Проверка загрузки CPU
            cpu_percent = psutil.cpu_percent(interval=1)
            logging.info(f"⚙️ Загрузка CPU: {cpu_percent}%")
            
            # Проверка процессов Chrome
            chrome_processes = [p for p in psutil.process_iter(['name']) if 'chrome' in p.info['name'].lower() or 'chromium' in p.info['name'].lower()]
            if chrome_processes:
                logging.warning(f"⚠️ Найдено {len(chrome_processes)} процессов Chrome/Chromium - возможен конфликт")
            else:
                logging.info("✅ Процессы Chrome не найдены - чистая система")
        except ImportError:
            logging.warning("⚠️ psutil не установлен, используем системные команды для диагностики")
            try:
                # Fallback: используем системные команды
                result = subprocess.run(['free', '-h'], capture_output=True, text=True, timeout=5)
                if result.returncode == 0:
                    logging.info(f"💾 Информация о памяти:\n{result.stdout}")
                    # Проверяем swap
                    for line in result.stdout.split('\n'):
                        if 'Swap:' in line:
                            swap_parts = line.split()
                            if len(swap_parts) >= 4:
                                swap_total = swap_parts[1]
                                swap_used = swap_parts[2]
                                swap_free = swap_parts[3]
                                # Если swap переполнен - это критично!
                                if swap_free and swap_free.replace('Mi', '').replace('Gi', '').replace('Ki', '').isdigit():
                                    free_val = int(swap_free.replace('Mi', '').replace('Gi', '').replace('Ki', ''))
                                    if free_val < 50:  # Меньше 50 MB свободно
                                        logging.error(f"❌ КРИТИЧНО: Swap переполнен! Свободно: {swap_free}, Использовано: {swap_used}/{swap_total}")
                                        logging.error("💡 Решение: Увеличьте swap или освободите память - это может вызывать падения Chrome!")
                
                result = subprocess.run(['ps', 'aux'], capture_output=True, text=True, timeout=5)
                if result.returncode == 0:
                    chrome_count = len([l for l in result.stdout.split('\n') if 'chrome' in l.lower() or 'chromium' in l.lower()])
                    if chrome_count > 0:
                        logging.warning(f"⚠️ Найдено {chrome_count} процессов Chrome/Chromium в системе")
                        # Если процессов много (больше 5), очищаем их
                        if chrome_count > 5:
                            logging.warning("🧹 Очищаем накопившиеся процессы Chrome...")
                            try:
                                _force_kill_chrome_processes()
                                logging.info("✅ Процессы Chrome очищены")
                            except Exception as e:
                                logging.warning(f"⚠️ Ошибка при очистке процессов: {e}")
                    else:
                        logging.info("✅ Процессы Chrome не найдены - чистая система")
            except Exception as e:
                logging.warning(f"⚠️ Не удалось выполнить диагностику через системные команды: {e}")
        except Exception as e:
            logging.warning(f"⚠️ Ошибка при диагностике системы: {e}")
    else:
        logging.info(f"ℹ️ Система: {platform.system()}, диагностика только для Linux")
    
    # ОБЯЗАТЕЛЬНАЯ очистка процессов Chrome перед инициализацией драйвера (для Linux)
    if platform.system() == 'Linux':
        logging.info("🧹 Очистка старых процессов Chrome перед запуском...")
        try:
            _force_kill_chrome_processes()
            logging.info("✅ Очистка завершена, система готова к запуску ChromeDriver")
        except Exception as e:
            logging.warning(f"⚠️ Ошибка при очистке процессов Chrome: {e}")

    logging.info("⚙️ Настройка ChromeOptions...")
    options = webdriver.ChromeOptions()
    
    if platform.system() == 'Linux':
        # В Docker нет X11: без headless Chrome падает с "session not created: Chrome instance exited"
        _hl = os.getenv("CHROME_HEADLESS", "true").strip().lower()
        if _hl in ("1", "true", "yes", "on"):
            options.add_argument("--headless=new")
            logging.info("Chrome: headless=new (CHROME_HEADLESS=%s)", _hl)
        else:
            logging.warning(
                "Chrome: headless отключён — нужен реальный DISPLAY/Xvfb; в Docker задайте CHROME_HEADLESS=true"
            )
        options.add_argument('--no-sandbox')  # Критично для работы на Linux сервере и snap
        options.add_argument('--disable-dev-shm-usage')  # Использует /tmp вместо /dev/shm
        options.add_argument('--disable-gpu')  # Отключает GPU
        options.add_argument('--disable-software-rasterizer')
        options.add_argument('--disable-extensions')
        # Важно для snap версии Chrome
        options.add_argument('--disable-crash-reporter')  # Отключает crash reporter
        options.add_argument('--disable-crashpad')  # Отключает crashpad
        options.add_argument('--remote-debugging-port=0')  # Отключает удаленную отладку
        options.add_argument('--disable-hang-monitor')  # Отключает мониторинг зависаний
        options.add_argument('--disable-prompt-on-repost')  # Отключает подсказки
        options.add_argument('--disable-background-downloads')  # Отключает фоновые загрузки
        options.add_argument('--disable-breakpad')  # Отключает breakpad
        options.add_argument('--disable-background-timer-throttling')
        options.add_argument('--disable-backgrounding-occluded-windows')
        options.add_argument('--disable-renderer-backgrounding')
        options.add_argument('--disable-features=TranslateUI,VizDisplayCompositor')
        options.add_argument('--disable-ipc-flooding-protection')
        options.add_argument('--disable-setuid-sandbox')
        options.add_argument('--disable-accelerated-2d-canvas')
        options.add_argument('--disable-accelerated-video-decode')
        options.add_argument('--disable-accelerated-video-encode')
        options.add_argument('--disable-3d-apis')  # Отключает WebGL
        options.add_argument('--disable-web-security')  # Для экономии ресурсов
        options.add_argument('--disable-features=AudioServiceOutOfProcess')
        
        # КРИТИЧЕСКИ ВАЖНЫЕ опции для предотвращения крашей при работе с DOM/JavaScript
        options.add_argument('--disable-features=IsolateOrigins,site-per-process')
        options.add_argument('--disable-site-isolation-trials')
        options.add_argument('--disable-features=VizDisplayCompositor')
        options.add_argument('--disable-features=UseChromeOSDirectVideoDecoder')
        options.add_argument('--disable-features=VaapiVideoDecoder')
        options.add_argument('--run-all-compositor-stages-before-draw')
        options.add_argument('--disable-threaded-compositing')
        options.add_argument('--disable-threaded-animation')
        options.add_argument('--disable-checker-imaging')
        options.add_argument('--renderer-process-limit=2')
        options.add_argument('--disable-smooth-scrolling')
        options.add_argument('--blink-settings=imagesEnabled=false')

        # Heap renderer: 256 часто мало → tab crashed; 384–512 стабильнее на VPS
        options.add_argument('--js-flags=--max-old-space-size=384')
        # Меньше viewport = меньше работы compositor
        options.add_argument('--window-size=1280,720')
        options.add_argument('--user-agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36')
        options.add_argument('--lang=uk-UA,uk;q=0.9,en-US;q=0.8,en;q=0.7')
    else:
        logging.info("🖥️ Обнаружен Windows/Mac - используем стандартные настройки Chrome")
        options.add_argument('--start-maximized')
        options.add_argument('--disable-infobars')
        options.add_argument('--disable-notifications')
        options.add_argument('--lang=uk-UA,uk;q=0.9,en-US;q=0.8,en;q=0.7')
        
    # Не ждать полной загрузки всех ресурсов (картинки/реклама) — меньше крашей
    options.page_load_strategy = "eager"
    # Общие настройки для всех ОС — маскировка от антибот-систем
    options.add_argument('--disable-blink-features=AutomationControlled')
    options.add_experimental_option('excludeSwitches', ['enable-automation', 'enable-logging'])
    options.add_experimental_option('useAutomationExtension', False)
    prefs = {
        'credentials_enable_service': False,
        'profile.password_manager_enabled': False,
        'profile.default_content_setting_values.notifications': 2,
        'profile.managed_default_content_settings.images': 2,
    }
    options.add_experimental_option('prefs', prefs)
    logging.info("✅ ChromeOptions готовы")

    if (not debug_mode) and PARSING_START_SMS_TEST_ENABLED:
        try:
            logging.info(
                "📤 Тестовая SMS в начале парсинга на номер %s",
                PARSING_START_SMS_TEST_NUMBER,
            )
            test_sms = _await(send_sms([PARSING_START_SMS_TEST_NUMBER]))
            if test_sms.get("success"):
                logging.info("✅ Тестовая SMS: TurboSMS вернул успех (response_code=0)")
            else:
                logging.warning(
                    "⚠️ Тестовая SMS не принята: %s",
                    test_sms.get("error_message") or test_sms.get("details"),
                )
        except Exception as test_exc:
            logging.error("❌ Ошибка тестовой SMS при старте парсинга: %s", test_exc)

    try:
        result = subprocess.run(
            ["google-chrome", "--version"],
            capture_output=True,
            text=True,
            timeout=8,
        )
        if result.returncode == 0:
            logging.info("🔍 Chrome версия: %s", result.stdout.strip())
        result = subprocess.run(
            ["chromedriver", "--version"],
            capture_output=True,
            text=True,
            timeout=8,
        )
        if result.returncode == 0:
            logging.info("🔍 ChromeDriver версия: %s", result.stdout.strip())
    except Exception as e:
        logging.warning("Не удалось получить версии Chrome/ChromeDriver: %s", e)

    driver = None
    logging.info("✅ Блок объявлений подтверждён, начинаем цикл обработки...")

    consecutive_errors = 0
    browser_ads_since_restart = 0

    # Функция для проверки работоспособности драйвера
    def check_driver_alive():
        """Проверяет, работает ли драйвер - упрощенная версия для Linux"""
        try:
            # Простая и быстрая проверка - пытаемся получить window_handles
            # Это не требует загрузки страницы и работает быстро
            # Используем короткий таймаут для избежания зависаний
            result = driver.window_handles
            if result is None:
                return False
            return True
        except (Exception, AttributeError, OSError):
            return False
    
    # Функция для переподключения драйвера
    def reconnect_driver(*, notify: bool = True):
        """Переподключает драйвер при ошибках с диагностикой причин падения"""
        nonlocal driver, browser_ads_since_restart
        import os
        import signal
        import subprocess
        
        # Диагностика: почему драйвер упал
        logging.warning("🔍 Диагностика причины падения драйвера...")
        
        if platform.system() == 'Linux':
            try:
                import psutil
                # Проверка памяти
                mem = psutil.virtual_memory()
                mem_available_gb = mem.available / (1024**3)
                logging.warning(f"💾 Память при падении: {mem_available_gb:.2f} GB свободно")
                if mem.available < 300 * 1024 * 1024:  # Меньше 300 MB
                    logging.error("❌ КРИТИЧНО: Очень мало памяти! Chrome был убит системой (OOM Killer)")
                    logging.error("💡 Решение: Увеличьте swap или освободите память")
                
                # Проверка процессов Chrome
                chrome_procs = [p for p in psutil.process_iter(['pid', 'name', 'memory_info']) 
                               if 'chrome' in p.info['name'].lower() or 'chromium' in p.info['name'].lower()]
                if chrome_procs:
                    total_mem = sum(p.info['memory_info'].rss for p in chrome_procs) / (1024**2)
                    logging.warning(f"⚠️ Найдено {len(chrome_procs)} процессов Chrome, занимают {total_mem:.0f} MB")
            except ImportError:
                logging.warning("⚠️ psutil не установлен, используем системные команды для диагностики")
                try:
                    # Fallback: используем системные команды
                    result = subprocess.run(['free', '-h'], capture_output=True, text=True, timeout=5)
                    if result.returncode == 0:
                        logging.warning(f"💾 Состояние памяти при падении:\n{result.stdout}")
                        # Парсим available память
                        for line in result.stdout.split('\n'):
                            if 'Mem:' in line:
                                parts = line.split()
                                if len(parts) >= 7:
                                    available = parts[6]
                                    logging.info(f"📊 Available память: {available}")
                            # Проверяем swap
                            if 'Swap:' in line:
                                swap_parts = line.split()
                                if len(swap_parts) >= 4:
                                    swap_total = swap_parts[1]
                                    swap_used = swap_parts[2]
                                    swap_free = swap_parts[3]
                                    # Если swap переполнен - это критично!
                                    if swap_free and swap_free.replace('Mi', '').replace('Gi', '').replace('Ki', '').isdigit():
                                        free_val = int(swap_free.replace('Mi', '').replace('Gi', '').replace('Ki', ''))
                                        if free_val < 100:  # Меньше 100 MB свободно
                                            logging.error(f"❌ КРИТИЧНО: Swap переполнен! Свободно: {swap_free}, Использовано: {swap_used}/{swap_total}")
                                            logging.error("💡 Это может быть причиной падения Chrome!")
                                            logging.error("💡 Решение: Увеличьте swap файл (см. doc/INSTALL_CHROME_UBUNTU.md)")
                        
                    # Проверяем логи системы на ошибки Chrome
                    try:
                        result = subprocess.run(['dmesg', '|', 'tail', '-20'], shell=True, 
                                               capture_output=True, text=True, timeout=3)
                        if result.returncode == 0 and ('chrome' in result.stdout.lower() or 'killed' in result.stdout.lower()):
                            logging.warning("⚠️ Найдены упоминания Chrome в логах системы")
                    except:
                        pass
                        
                    # Проверяем процессы Chrome
                    result = subprocess.run(['ps', 'aux'], capture_output=True, text=True, timeout=5)
                    if result.returncode == 0:
                        chrome_lines = [l for l in result.stdout.split('\n') if 'chrome' in l.lower() or 'chromium' in l.lower()]
                        if chrome_lines:
                            logging.warning(f"⚠️ Найдено {len(chrome_lines)} процессов Chrome/Chromium в системе")
                            for line in chrome_lines[:3]:  # Показываем первые 3
                                logging.debug(f"   {line[:100]}")  # Первые 100 символов
                except Exception as e:
                    logging.warning(f"⚠️ Не удалось проверить память: {e}")
            except Exception as e:
                logging.warning(f"⚠️ Ошибка диагностики: {e}")
        
        # Дополнительная диагностика: проверяем почему драйвер падает
        logging.info("💡 Возможные причины падения ChromeDriver:")
        logging.info("   1. Chrome крашится из-за ошибок на странице")
        logging.info("   2. Проблемы с рендерингом JavaScript")
        logging.info("   3. Таймауты при поиске элементов")
        logging.info("   4. Несовместимость версий Chrome/ChromeDriver")
        
        try:
            logging.warning("🧹 Закрытие старого драйвера и очистка Chrome/zombie...")
            _safe_quit_driver(driver)
            driver = None
            logging.info("✅ Старый драйвер закрыт, процессы очищены")
        except Exception as e:
            logging.debug(f"Ошибка при закрытии старого драйвера: {e}")
            driver = None
            _force_kill_chrome_processes()
        
        try:
            logging.warning("🔄 Переподключение ChromeDriver...")
            if notify:
                _notify_user("🔄 Перезапуск браузера (драйвер упал)...")
            driver = webdriver.Chrome(options=options)
            driver.set_page_load_timeout(60)
            driver.implicitly_wait(10)
            driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {'source': '''
                Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
                Object.defineProperty(navigator, 'languages', {get: () => ['uk-UA', 'uk', 'en-US', 'en']});
                Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5]});
                window.chrome = {runtime: {}};
                const originalQuery = window.navigator.permissions.query;
                window.navigator.permissions.query = (parameters) =>
                    parameters.name === 'notifications'
                        ? Promise.resolve({state: Notification.permission})
                        : originalQuery(parameters);
            '''})
            logging.info("✅ ChromeDriver переподключен, stealth-скрипты применены")
            
            # Проверяем, что новый драйвер работает
            try:
                _ = driver.window_handles
                logging.info("✅ Новый драйвер отвечает")
            except Exception:
                logging.error("❌ Новый драйвер не отвечает")
                return False

            browser_ads_since_restart = 0
            return True
        except Exception as e:
            logging.error(f"❌ Не удалось переподключить ChromeDriver: {e}")
            logging.error("💡 Возможные причины:")
            logging.error("   1. Нехватка памяти на сервере")
            logging.error("   2. Chrome/ChromeDriver не установлен")
            logging.error("   3. Несовместимость версий")
            logging.error("   4. Проблемы с правами доступа")
            return False

    # БЕСКОНЕЧНЫЙ ЦИКЛ ПАРСИНГА
    iteration = 0
    total_processed_ads = 0
    total_ads_with_phones = 0
    total_ads_without_phones = 0
    daily_limit_state = {
        "notified": False,
        "notified_80": False,
        "deferred_notified": False,
        "date": None,
    }  # не спамить про лимит / deferred каждый ад
    
    import random
    
    while True:
        # Проверка на критическое количество ошибок
        if consecutive_errors >= 3:
            logging.warning(f"⚠️ Обнаружено {consecutive_errors} ошибок подряд!")
            consecutive_errors = 0
            _sleep(60)

        iteration += 1
        logging.info(f"🔄 Итерация парсинга #{iteration}")
        
        # Проверяем, не остановил ли пользователь парсинг
        if stop_event.is_set():
            logging.info("🛑 Парсинг остановлен пользователем.")
            break
        
        # Инициализируем драйвер для новой итерации (если он не создан) - УБРАНО, теперь инициализируем внутри цикла по объявлениям
        # if driver is None:
        #    ...

        # Счетчики для статистики текущей итерации
        processed_ads = 0
        ads_with_phones = 0
        ads_without_phones = 0

        # Запрос к API для получения объявлений (несколько страниц)
        try:
            logging.info(
                "🔍 Получение списка объявлений из API (до %s стр.)...",
                PARSING_SEARCH_PAGES,
            )
            ads_list = _await(
                search_extended_pages(
                    year_from=str(year_first),
                    year_to=str(year_second),
                    category=int(typeavto) if str(typeavto).isdigit() else 1,
                    region=region_param,
                    price_first=float(price_first),
                    price_second=float(price_second),
                    bodystyle=[],
                    pages=PARSING_SEARCH_PAGES,
                    republished_last=PARSING_REPUBLISHED_LAST,
                )
            )
            logging.info("📋 К обработке уникальных объявлений: %s", len(ads_list))
        except Exception as ex:
            consecutive_errors += 1
            logging.error(f"❌ Ошибка при вызове search_extended_pages: {ex}")
            # Пауза перед следующей попыткой (как между итерациями)
            pause_time = random.randint(pause_iter_min, pause_iter_max)
            if pause_time >= 60:
                logging.info(f"⏳ Ожидание {pause_time // 60} минут перед следующей попыткой...")
            else:
                logging.info(f"⏳ Ожидание {pause_time} с перед следующей попыткой...")
            for _ in range(pause_time):
                if stop_event.is_set():
                    break
                time.sleep(1)
            continue

        if not ads_list:
            logging.info("📭 Объявления не найдены в этой итерации")
            pause_time = random.randint(pause_iter_min, pause_iter_max)
            if pause_time >= 60:
                logging.info(f"⏳ Ожидание {pause_time // 60} минут перед следующей попыткой...")
            else:
                logging.info(f"⏳ Ожидание {pause_time} с перед следующей попыткой...")
            for _ in range(pause_time):
                if stop_event.is_set():
                    break
                time.sleep(1)
            continue

        # Обрабатываем каждое объявление
        for ad in ads_list:
            if stop_event.is_set():
                logging.info("🛑 Парсинг остановлен пользователем во время обработки объявлений.")
                break

            try:
                processed_ads += 1
                used_browser_for_ad = False

                def _count_and_maybe_restart_chrome():
                    nonlocal driver, browser_ads_since_restart, used_browser_for_ad
                    if not used_browser_for_ad:
                        return
                    browser_ads_since_restart += 1
                    used_browser_for_ad = False  # не считать повторно в конце того же объявления
                    if (
                        CHROME_RESTART_EVERY_ADS > 0
                        and browser_ads_since_restart >= CHROME_RESTART_EVERY_ADS
                        and driver is not None
                    ):
                        logging.info(
                            "🔄 Плановый рестарт Chrome после %s открытых объявлений",
                            browser_ads_since_restart,
                        )
                        try:
                            _safe_quit_driver(driver)
                        except Exception:
                            pass
                        driver = None
                        browser_ads_since_restart = 0

                info = extract_car_info(ad)
                link = info.get('link')

                if not link or not isinstance(link, str) or not link.strip():
                    logging.warning(f"Объявление {info.get('id')}: ссылка отсутствует или некорректна: {link}")
                    continue

                if PARSING_SKIP_NEWAUTO and "/newauto/" in link.lower():
                    logging.info(
                        "⏭️ Объявление %s — newauto (салон), пропуск (PARSING_SKIP_NEWAUTO=true): %s",
                        info.get("id"),
                        link,
                    )
                    continue

                ad_id_str = str(info.get('id') or "")
                # Пропуск только после успешной SMS; при фейле — ретрай по сохранённым номерам (без Chrome)
                if ad_id_str and not debug_mode:
                    stored_phones = _await(get_autoinfo_phones(ad_id_str))
                    if stored_phones:
                        from spam_sms import extract_clean_phones
                        from app.database.crud_static import is_phone_sent

                        clean_stored = extract_clean_phones(stored_phones)
                        if clean_stored and _await(
                            is_phone_sent(
                                clean_stored[0], effective_sms_resend_after_days()
                            )
                        ):
                            logging.info(
                                "⏭️ Объявление %s — SMS уже успешна на %s — пропуск "
                                "(SMS_SKIP_ALREADY_SENT=%s)",
                                ad_id_str,
                                clean_stored[0],
                                SMS_SKIP_ALREADY_SENT,
                            )
                            continue
                        if clean_stored:
                            logging.info(
                                "🔁 Объявление %s — телефоны в БД, SMS ещё не ок — ретрай без Chrome",
                                ad_id_str,
                            )
                            ads_with_phones += 1
                            sms_outcome = _try_ad_sms(
                                info=info,
                                link=link,
                                all_phones=clean_stored,
                                debug_mode=debug_mode,
                                daily_limit_state=daily_limit_state,
                                _await=_await,
                                _notify_user=_notify_user,
                                user_id=user_id,
                                work_from=(filter_settings or {}).get("work_from"),
                                work_to=(filter_settings or {}).get("work_to"),
                                work_days=(filter_settings or {}).get("work_days"),
                            )
                            if sms_outcome == "sent" and not debug_mode:
                                _persist_autoinfo_after_sms_ok(
                                    info, link, clean_stored, _await
                                )
                            logging.info("Объявление %s обработано (SMS-ретрай)", info.get("id"))
                            _pause_after_ad_outcome(sms_outcome, stop_event)
                            continue

                # Инициализация драйвера ПЕРЕД каждым объявлением (если он закрыт)
                if driver is None:
                    logging.info("🚀 Запуск браузера...")
                    try:
                        driver = webdriver.Chrome(options=options)
                        driver.set_page_load_timeout(120)
                        driver.implicitly_wait(30)
                        # Stealth: убираем маркеры автоматизации
                        driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {'source': '''
                            Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
                            Object.defineProperty(navigator, 'languages', {get: () => ['uk-UA', 'uk', 'en-US', 'en']});
                            Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5]});
                            window.chrome = {runtime: {}};
                            const originalQuery = window.navigator.permissions.query;
                            window.navigator.permissions.query = (parameters) =>
                                parameters.name === 'notifications'
                                    ? Promise.resolve({state: Notification.permission})
                                    : originalQuery(parameters);
                        '''})
                        logging.info("✅ Браузер запущен, stealth-скрипты применены")
                        # Прогрев: заходим на главную для получения cookie
                        logging.info("🌐 Прогрев: загружаем главную страницу auto.ria.com...")
                        try:
                            driver.get("https://auto.ria.com/uk/")
                            _sleep(10)
                            # Принимаем cookie-баннер на главной
                            try:
                                driver.implicitly_wait(0)
                                _cb = driver.find_elements(By.XPATH,
                                    "//button[contains(text(), 'Розумію і дозволяю') or "
                                    "contains(text(), 'Понимаю и разрешаю') or "
                                    "contains(text(), 'Accept')]"
                                )
                                if _cb:
                                    _cb[0].click()
                                    logging.info("🍪 Cookie-баннер принят на главной")
                                    _sleep(3)
                                driver.implicitly_wait(30)
                            except Exception:
                                driver.implicitly_wait(30)
                            logging.info("✅ Прогрев завершён, cookie получены")
                        except Exception as _warm_e:
                            logging.warning("⚠️ Не удалось прогреть браузер: %s", _warm_e)
                    except Exception as e:
                        logging.error(f"❌ Не удалось запустить браузер: {e}")
                        continue

                if not check_driver_alive():
                    logging.warning("⚠️ Драйвер не отвечает, пытаемся переподключиться...")
                    if not reconnect_driver():
                        logging.error("❌ Не удалось переподключить драйвер, пропускаем объявление")
                        continue

                logging.info(f"🌐 Переходим на страницу объявления: {info.get('name', 'Неизвестно')}")
                logging.info(f"🔗 Ссылка: {link}")

                try:
                    logging.info(f"🚀 Выполняем driver.get('{link}')...")
                    if stop_event.is_set():
                        break
                    driver.set_page_load_timeout(45)
                    driver.get(link)
                    logging.info(f"📍 Текущий URL после навигации: {driver.current_url}")
                    consecutive_errors = 0

                    # Проверка антибот-страницы + cookie-баннер
                    _antibot_retries = 0
                    _max_antibot_retries = 3
                    while _antibot_retries < _max_antibot_retries:
                        try:
                            _page_title = driver.title or ""
                        except Exception:
                            _page_title = ""
                        if "підозрілих" in _page_title or "подозрительных" in _page_title:
                            _antibot_retries += 1
                            _wait_sec = 60 * _antibot_retries
                            logging.warning(
                                "🛡️ Антибот-страница обнаружена! Попытка %d/%d — ждём %d секунд...",
                                _antibot_retries, _max_antibot_retries, _wait_sec
                            )
                            for _ in range(_wait_sec):
                                if stop_event.is_set():
                                    break
                                time.sleep(1)
                            if stop_event.is_set():
                                break
                            logging.info("🔄 Повторная загрузка страницы после антибот-паузы...")
                            try:
                                driver.get(link)
                                _sleep(10)
                            except Exception:
                                break
                        else:
                            break

                    # Если после всех попыток всё ещё антибот — пропускаем
                    try:
                        _page_title = driver.title or ""
                    except Exception:
                        _page_title = ""
                    if "підозрілих" in _page_title or "подозрительных" in _page_title:
                        logging.error(
                            "🛡️ Не удалось обойти антибот-защиту для %s. "
                            "Перезапускаем браузер и пропускаем.",
                            info.get('id')
                        )
                        _notify_user(
                            f"🛡️ Антибот-защита на объявлении {info.get('id')}.\n"
                            f"Перезапуск браузера, пауза 2 мин..."
                        )
                        try:
                            _safe_quit_driver(driver)
                        except Exception:
                            pass
                        driver = None
                        _antibot_pause = 120
                        logging.info("⏳ Пауза %d секунд перед следующим объявлением (антибот cooldown)...", _antibot_pause)
                        for _ in range(_antibot_pause):
                            if stop_event.is_set():
                                break
                            time.sleep(1)
                        continue

                    # Принимаем cookie-баннер если есть
                    try:
                        driver.implicitly_wait(0)
                        _cookie_btns = driver.find_elements(By.XPATH,
                            "//button[contains(text(), 'Розумію і дозволяю') or "
                            "contains(text(), 'Понимаю и разрешаю') or "
                            "contains(text(), 'Accept')]"
                        )
                        if _cookie_btns:
                            _cookie_btns[0].click()
                            logging.info("🍪 Cookie-баннер принят")
                            _sleep(2)
                        driver.implicitly_wait(30)
                    except Exception:
                        driver.implicitly_wait(30)

                    logging.info("✅ Страница загружена, начинаем ожидание...")
                except Exception as e:
                    consecutive_errors += 1
                    error_msg = str(e).lower()
                    is_fatal_browser = any(m in error_msg for m in _FATAL_BROWSER_ERROR_MARKERS)
                    needs_reconnect = (
                        is_fatal_browser
                        or "connection refused" in error_msg
                        or "connection" in error_msg
                        or "timeout" in error_msg
                        or consecutive_errors >= 3
                    )

                    logging.error("Ошибка при переходе по ссылке %s: %s", link, e)

                    if needs_reconnect:
                        logging.warning(
                            "🔄 Браузер неработоспособен (fatal=%s, consecutive=%s/3) — немедленный рестарт...",
                            is_fatal_browser,
                            consecutive_errors,
                        )
                        try:
                            _safe_quit_driver(driver)
                        except Exception:
                            pass
                        driver = None

                        if consecutive_errors >= 3:
                            logging.error(
                                "❌ Достигнут лимит ошибок (%s/3). Прерываем итерацию для перезапуска драйвера.",
                                consecutive_errors,
                            )
                            _notify_user(
                                f"🔄 Перезапуск итерации: {consecutive_errors} ошибки подряд при загрузке страниц.\n"
                                f"Браузер будет перезапущен."
                            )
                            break

                        if reconnect_driver(notify=is_fatal_browser):
                            consecutive_errors = 0
                            # tab crashed на конкретной странице часто повторяется —
                            # не долбим ту же ссылку, идём к следующему объявлению
                            if is_fatal_browser:
                                logging.warning(
                                    "⏭️ Объявление %s пропущено после tab crash "
                                    "(браузер перезапущен для следующих)",
                                    info.get("id"),
                                )
                                _sleep(3)
                                continue
                            try:
                                driver.get(link)
                                logging.info(
                                    "✅ Страница загружена после переподключения, ждем %sс...",
                                    PARSING_RECONNECT_WAIT_SEC,
                                )
                                _sleep(PARSING_RECONNECT_WAIT_SEC)
                            except Exception as e2:
                                logging.error("❌ Ошибка после переподключения: %s", e2)
                                try:
                                    _safe_quit_driver(driver)
                                except Exception:
                                    pass
                                driver = None
                                continue
                        else:
                            logging.error("❌ Не удалось переподключить драйвер, пропускаем объявление")
                            continue
                    else:
                        continue

                used_browser_for_ad = True
                wait_timeout = PARSING_PAGE_WAIT_SEC
                logging.info(f"⏳ Ожидание загрузки страницы ({wait_timeout} секунд)...")

                # Функция для проверки работоспособности драйвера (локальная)
                def check_driver_alive_local():
                    """Проверяет, работает ли драйвер - упрощенная версия"""
                    try:
                        # Простая и быстрая проверка - пытаемся получить window_handles
                        # Это не требует загрузки страницы и работает быстро
                        result = driver.window_handles
                        if result is None:
                            return False
                        return True
                    except (Exception, AttributeError, OSError):
                        return False

                waited = 0.0
                while waited < wait_timeout:
                    try:
                        if not check_driver_alive_local():
                            logging.warning(f"⚠️ Драйвер упал во время ожидания (прошло {waited}/{wait_timeout} сек)")
                            if reconnect_driver():
                                try:
                                    driver.get(link)
                                    logging.info("✅ Страница перезагружена после переподключения")
                                    waited = 0.0
                                    time.sleep(1)
                                    waited += 1
                                except Exception:
                                    logging.error("❌ Не удалось перезагрузить страницу, пропускаем объявление")
                                    break
                            else:
                                logging.error("❌ Не удалось переподключить драйвер, пропускаем объявление")
                                break

                        if stop_event.is_set():
                            break
                        time.sleep(1)
                        waited += 1
                        if waited % 10 < 1:
                            logging.info(f"⏳ Ждем... {waited:.0f}/{wait_timeout}")

                    except KeyboardInterrupt:
                        raise
                    except Exception as e:
                        error_msg = str(e).lower()
                        if 'connection' in error_msg or 'refused' in error_msg:
                            logging.warning("⚠️ Ошибка соединения во время ожидания, пытаемся переподключиться...")
                            if reconnect_driver():
                                try:
                                    driver.get(link)
                                    waited = 0.0
                                    _sleep(5)
                                    waited += 5
                                except Exception:
                                    break
                            else:
                                break
                        else:
                            if stop_event.is_set():
                                break
                            time.sleep(1)
                            waited += 1

                logging.info(f"✅ Ожидание завершено ({waited} сек). Проверяем состояние перед скроллингом...")
                
                try:
                    driver_alive = check_driver_alive_local()
                except Exception:
                    driver_alive = False

                if not driver_alive:
                    logging.warning("⚠️ Драйвер не отвечает после ожидания, пытаемся переподключиться...")
                    if reconnect_driver():
                        try:
                            driver.get(link)
                            logging.info(
                                "✅ Страница перезагружена после переподключения, ждем %sс...",
                                PARSING_RECONNECT_WAIT_SEC,
                            )
                            _sleep(PARSING_RECONNECT_WAIT_SEC)
                        except Exception as e:
                            logging.error(f"❌ Не удалось перезагрузить страницу: {e}, пропускаем объявление")
                            _count_and_maybe_restart_chrome()
                            continue
                    else:
                        logging.error("❌ Не удалось переподключить драйвер, пропускаем объявление")
                        _count_and_maybe_restart_chrome()
                        continue

                try:
                    driver.execute_script("window.scrollTo(0, 500)")
                    logging.info("📜 Скроллинг выполнен, ждем %s секунд...", PARSING_SCROLL_WAIT_SEC)
                    if PARSING_SCROLL_WAIT_SEC:
                        _sleep(PARSING_SCROLL_WAIT_SEC)
                except Exception as e:
                    logging.warning(f"⚠️ Ошибка при скроллинге: {e}")

                logging.info("🏁 Скроллинг завершен, переходим к поиску кнопки телефона...")

                # Переходим к поиску кнопки
                
                if not check_driver_alive_local():
                    logging.warning("⚠️ Драйвер не отвечает перед поиском кнопки, пытаемся переподключиться...")
                    if reconnect_driver():
                        try:
                            driver.get(link)
                            logging.info(
                                "⏳ Ожидание %sс после перезагрузки страницы...",
                                PARSING_RECONNECT_WAIT_SEC,
                            )
                            _sleep(PARSING_RECONNECT_WAIT_SEC)
                        except Exception:
                            logging.error("❌ Не удалось перезагрузить страницу, пропускаем объявление")
                            _count_and_maybe_restart_chrome()
                            continue
                    else:
                        logging.error("❌ Не удалось переподключить драйвер, пропускаем объявление")
                        _count_and_maybe_restart_chrome()
                        continue

                # Пробуем найти и кликнуть по кнопке показа телефона
                # Используем несколько вариантов поиска для надежности
                logging.info("🔎 Начинаем поиск кнопки 'Показать телефон'...")
                button_clicked = False
                button_selectors = [
                    (By.XPATH, "//a[contains(@href, 'tel:')]//span[contains(text(), 'XXX')]"),
                    (By.XPATH, "//button[.//span[contains(text(), 'XXX')]]"),
                    (By.XPATH, "//a[contains(@href, 'tel:')]//button"),
                    (By.CSS_SELECTOR, "a[href^='tel:'] button"),
                    (By.CSS_SELECTOR, ".seller_info_phones a[href^='tel:']"),
                    (By.CSS_SELECTOR, ".phones_show_seller"),
                    (By.CSS_SELECTOR, "[data-action='sellerPhoneClick']"),
                    (By.XPATH, '/html/body/div/main/div[1]/div[2]/div[2]/div[3]/div[6]/button'),  # 🆕 Специфичный селектор от пользователя
                    (By.XPATH, '/html/body/div/main/div[1]/div[2]/div[2]/div[3]/div[4]/button'),  # Приоритетный селектор от пользователя
                    (By.XPATH, '/html/body/div/main/div[1]/div[3]/div[2]/div[3]/div[4]/button'),
                    (By.XPATH, '/html/body/div/main/div[1]/div[3]/div[2]/div[3]/div[5]/button'),
                    (By.XPATH, '/html/body/div/main/div[1]/div[3]/div[2]/div[3]/div/button'),
                    (By.XPATH, "//button[contains(text(), 'Показать телефон')]"),
                    (By.XPATH, "//button[contains(text(), 'Показати телефон')]"),
                    (By.XPATH, "//button//span[contains(text(), 'XXX')]"), # 🆕 Для кнопок с маской (099) XXX...
                    (By.XPATH, "//a[contains(text(), 'Показать телефон')]"),
                    (By.XPATH, "//a[contains(text(), 'Показати телефон')]"),
                    (By.CSS_SELECTOR, 'button.show-phones'),
                    (By.CSS_SELECTOR, 'a.show-phones'),
                    (By.CSS_SELECTOR, '.show-phones'),
                    (By.XPATH, '/html/body/div/main/div[1]/div[2]/div[2]/div[3]/div[5]/button'),
                    (By.XPATH, '/html/body/div[7]/div[10]/div[4]/main/div[12]/div/div[1]/div/a'),
                    (By.CSS_SELECTOR, "[data-action='show-phone']"),
                    (By.CSS_SELECTOR, "[data-action='showPhones']"),
                    (By.CSS_SELECTOR, "[data-action='showBottomPopUp']"), # 🆕 Перемещено вниз списка
                ]

                # Временно отключаем implicit wait для ускорения перебора
                driver.implicitly_wait(0)
                
                # Сначала пробуем конкретные XPath
                for i, (selector_type, selector_value) in enumerate(button_selectors):
                    if stop_event.is_set():
                        logging.info("🛑 Стоп — прерываем поиск кнопки телефона")
                        break
                    try:
                        logging.info(f"🔎 Проверка селектора #{i+1}...")
                        
                        try:
                            from selenium.webdriver.support.ui import WebDriverWait
                            from selenium.webdriver.support import expected_conditions as EC
                            wait = WebDriverWait(driver, 2)
                            button = wait.until(EC.presence_of_element_located((selector_type, selector_value)))
                        except Exception:
                            # Быстрая проверка без ожидания, если wait.until упал
                            try:
                                button = driver.find_element(selector_type, selector_value)
                            except Exception:
                                continue

                        if not check_driver_alive_local():
                            logging.warning("⚠️ Драйвер упал сразу после поиска элемента")
                            break

                        if button.is_displayed() and button.is_enabled():
                            if not check_driver_alive_local():
                                break
                            driver.execute_script("arguments[0].click();", button)
                            button_clicked = True
                            logging.info(f"✅ Кнопка найдена и кликнута по селектору #{i+1}")
                            break
                    except Exception as e:
                        error_msg = str(e).lower()
                        if any(
                            m in error_msg
                            for m in (
                                "connection refused",
                                "connection aborted",
                                "max retries exceeded",
                                "chrome not reachable",
                            )
                        ) or any(m in error_msg for m in _FATAL_BROWSER_ERROR_MARKERS):
                            if stop_event.is_set():
                                logging.info(
                                    "🛑 Стоп + мёртвый Chrome — выходим из поиска кнопки"
                                )
                                break
                            logging.warning("⚠️ Ошибка соединения при поиске кнопки")
                            if reconnect_driver():
                                try:
                                    driver.get(link)
                                    _sleep(PARSING_RECONNECT_WAIT_SEC)
                                except Exception:
                                    break
                                continue
                            else:
                                break
                        continue
                
                # Если не нашли по основным селекторам, пробуем гибкий поиск
                if not button_clicked and not stop_event.is_set():
                    logging.info("🔎 Основные селекторы не сработали, пробуем гибкий поиск по div/button...")
                    try:
                        base_path = '/html/body/div/main/div[1]/div[3]/div[2]/div[3]'
                        for div_index in range(1, 10):
                            if stop_event.is_set():
                                break
                            try:
                                xpath = f"{base_path}/div[{div_index}]/button"
                                button = driver.find_element(By.XPATH, xpath)
                                if button.is_displayed() and button.is_enabled():
                                    button_text = button.text.lower()
                                    if any(word in button_text for word in ['показать', 'показати', 'телефон', 'phone', 'call']):
                                        driver.execute_script("arguments[0].click();", button)
                                        button_clicked = True
                                        logging.info(f"✅ Кнопка найдена через гибкий поиск: {xpath} (текст: {button.text})")
                                        break
                            except Exception as flex_exc:
                                flex_msg = str(flex_exc).lower()
                                if "connection refused" in flex_msg or "max retries" in flex_msg:
                                    break
                                continue
                    except Exception:
                        pass
                    logging.info("🔎 Гибкий поиск завершён, результат: %s", "найдена" if button_clicked else "не найдена")

                # Возвращаем умеренный implicit wait (не 30с — слишком медленно на misses)
                try:
                    driver.implicitly_wait(8)
                except Exception:
                    pass

                if stop_event.is_set():
                    logging.info("🛑 Стоп после поиска кнопки — выходим из объявления")
                    break

                if not button_clicked:
                    logging.warning(f"⚠️ Не удалось найти кнопку показа телефона для объявления {info.get('id')} ({link})")
                    logging.warning("💡 Проверьте браузер - возможно структура страницы изменилась")
                    logging.info("🔬 Запускаем HTML-диагностику для определения актуальных селекторов...")
                    try:
                        _diag_html = driver.page_source
                        _diag_soup = BeautifulSoup(_diag_html, 'html.parser')
                        _found_diag = False
                        for _tag in _diag_soup.find_all(string=re.compile(r'XXX')):
                            _parent = _tag.parent
                            if _parent:
                                for _ in range(5):
                                    if _parent.parent:
                                        _parent = _parent.parent
                                logging.info("DIAG XXX element:\n%s", str(_parent)[:2000])
                                _found_diag = True
                                break
                        for _tel in _diag_soup.select("a[href^='tel:']")[:3]:
                            logging.info("DIAG tel link: %s", str(_tel)[:500])
                            _found_diag = True
                        for _btn in _diag_soup.select("button")[:10]:
                            _btn_text = _btn.get_text(strip=True)[:80]
                            _btn_classes = _btn.get('class', [])
                            if _btn_text or _btn_classes:
                                logging.info("DIAG button: classes=%s text='%s'", _btn_classes, _btn_text)
                                _found_diag = True
                        if not _found_diag:
                            logging.warning("DIAG: Не найдено ни XXX, ни tel: ссылок, ни кнопок — возможно, страница не загрузилась")
                            logging.info("DIAG page title: %s", driver.title)
                            logging.info("DIAG URL: %s", driver.current_url)
                            logging.info("DIAG HTML length: %d chars", len(_diag_html))
                    except Exception as _de:
                        logging.debug("Диагностика не удалась: %s", _de)
                    logging.info("🔬 HTML-диагностика завершена")
                else:
                    logging.info(
                        "⏳ Ждем %s секунд после клика для появления телефонов...",
                        PARSING_AFTER_CLICK_WAIT_SEC,
                    )
                    _sleep(PARSING_AFTER_CLICK_WAIT_SEC)

                # Ждем появления телефонов после клика на кнопку
                logging.info("🔍 Начинаем поиск телефонов на странице...")
                all_phones = []

                try:
                    if not check_driver_alive_local():
                        if not reconnect_driver():
                            logging.warning("⚠️ Драйвер не отвечает, пропускаем поиск телефонов")
                        else:
                            driver.get(link)
                            logging.info(
                                "⏳ Ожидание %sс после перезагрузки для поиска телефонов...",
                                PARSING_RECONNECT_WAIT_SEC,
                            )
                            _sleep(PARSING_RECONNECT_WAIT_SEC)

                    if check_driver_alive_local():
                        from selenium.webdriver.support.ui import WebDriverWait
                        from selenium.webdriver.support import expected_conditions as EC

                        driver.implicitly_wait(0)
                        logging.info("📞 Поиск телефонов: этап 1 — XPath-ы на span с номером...")
                        wait = WebDriverWait(driver, PARSING_PHONE_FIND_WAIT_SEC)
                        phone_xpaths = [
                            '/html/body/div/main/div[3]/div/div[2]/div[2]/a[1]/div/button/span',
                            '/html/body/div/main/div[3]/div/div[2]/div[2]/a[2]/div/button/span',
                            '/html/body/div/main/div[3]/div/div[2]/div[2]/a[3]/div/button/span',
                        ]

                        for xpath in phone_xpaths:
                            try:
                                phone_elements = driver.find_elements(By.XPATH, xpath)
                                for element in phone_elements or []:
                                    phone_text = element.text.strip()
                                    if phone_text and any(c.isdigit() for c in phone_text) and len(phone_text) > 5:
                                        all_phones.append(phone_text)
                                        logging.info(f"✅ Найден телефон через XPath: {phone_text}")
                                    else:
                                        logging.debug(f"⚠️ Найден текст, но это не телефон: '{phone_text}'")
                            except Exception as e:
                                error_msg = str(e).lower()
                                if 'connection' in error_msg and reconnect_driver():
                                    continue

                        if not all_phones:
                            logging.info(
                                "📞 Поиск телефонов: этап 2 — общий XPath (ожидание до %sс)...",
                                PARSING_PHONE_FIND_WAIT_SEC,
                            )
                            try:
                                phone_elements = wait.until(
                                    EC.presence_of_all_elements_located((By.XPATH, '/html/body/div/main/div[3]/div/div[2]/div[2]//a/div/button/span'))
                                )
                                for element in phone_elements or []:
                                    phone_text = element.text.strip()
                                    if phone_text and any(c.isdigit() for c in phone_text) and len(phone_text) > 5:
                                        all_phones.append(phone_text)
                                        logging.info(f"✅ Найден телефон через общий XPath: {phone_text}")
                            except Exception as e:
                                error_msg = str(e).lower()
                                if 'connection' not in error_msg:
                                    logging.debug(f"Не найдено телефонов по общему XPath: {e}")

                        driver.implicitly_wait(8)
                except Exception as e:
                    error_msg = str(e).lower()
                    if 'connection' not in error_msg:
                        logging.debug(f"Не найдено телефонов по основному XPath: {e}")

                if not all_phones:
                    logging.info(
                        "📞 Поиск телефонов: этап 3 — popup-successful-call-desk (ожидание до %sс)...",
                        PARSING_PHONE_FIND_WAIT_SEC,
                    )
                    try:
                        wait = WebDriverWait(driver, PARSING_PHONE_FIND_WAIT_SEC)
                        phone_elements = wait.until(
                            EC.presence_of_all_elements_located((By.CLASS_NAME, 'popup-successful-call-desk'))
                        )
                        for element in phone_elements or []:
                            phone_text = element.text.strip()
                            if phone_text and any(c.isdigit() for c in phone_text) and len(phone_text) > 5:
                                all_phones.append(phone_text)
                                logging.info(f"✅ Найден телефон через класс: {phone_text}")
                    except Exception:
                        logging.info("📞 popup-successful-call-desk не найден")

                if not all_phones:
                    if PARSING_STAGE_PAUSE_SEC:
                        logging.info("📞 Пауза %sс перед следующим этапом поиска...", PARSING_STAGE_PAUSE_SEC)
                        _sleep(PARSING_STAGE_PAUSE_SEC)
                    logging.info("📞 Поиск телефонов: этап 4 — ссылки a[href^='tel:']...")
                    driver.implicitly_wait(0)
                    try:
                        tel_links = driver.find_elements(By.CSS_SELECTOR, "a[href^='tel:']")
                        logging.info("📞 Найдено %d tel: ссылок", len(tel_links))
                        for tel_link in tel_links:
                            href = tel_link.get_attribute('href')
                            if href and href.startswith('tel:'):
                                phone = href.replace('tel:', '').strip()
                                if phone and any(c.isdigit() for c in phone) and len(phone) > 5:
                                    all_phones.append(phone)
                                    logging.info(f"✅ Найден телефон через tel: {phone}")
                                else:
                                    logging.debug(f"tel: ссылка с маскированным номером: {phone}")
                    except Exception as e:
                        logging.debug(f"Ошибка при поиске tel: ссылок: {e}")
                    driver.implicitly_wait(8)

                if not all_phones:
                    if PARSING_STAGE_PAUSE_SEC:
                        logging.info("📞 Пауза %sс перед следующим этапом поиска...", PARSING_STAGE_PAUSE_SEC)
                        _sleep(PARSING_STAGE_PAUSE_SEC)
                    logging.info("📞 Поиск телефонов: этап 5 — CSS-селекторы с phone/tel...")
                    driver.implicitly_wait(0)
                    try:
                        selectors = [
                            '.popup-successful-call-desk',
                            "[class*='phone']",
                            "[class*='tel']",
                            '.phone-number',
                            '.contact-phone',
                        ]
                        for selector in selectors:
                            try:
                                elements = driver.find_elements(By.CSS_SELECTOR, selector)
                                for elem in elements:
                                    phone_text = elem.text.strip()
                                    if phone_text and any(c.isdigit() for c in phone_text) and len(phone_text) > 5:
                                        all_phones.append(phone_text)
                                        logging.info(f"✅ Найден телефон через селектор {selector}: {phone_text}")
                            except Exception as e:
                                error_msg = str(e).lower()
                                if 'connection' in error_msg and reconnect_driver():
                                    break
                                continue
                    except Exception:
                        pass
                    driver.implicitly_wait(8)

                if not all_phones:
                    if PARSING_STAGE_PAUSE_SEC:
                        logging.info("📞 Пауза %sс перед следующим этапом поиска...", PARSING_STAGE_PAUSE_SEC)
                        _sleep(PARSING_STAGE_PAUSE_SEC)
                    logging.info("📞 Поиск телефонов: этап 6 — HTML-парсинг регулярным выражением...")
                    try:
                        page_source = driver.page_source
                        soup = BeautifulSoup(page_source, 'html.parser')
                        phone_candidates = soup.find_all(text=re.compile(r'\+?3?8?0[\d\-\s\(\)]{9,}'))
                        logging.info("📞 Найдено %d кандидатов через regex", len(phone_candidates))
                        for candidate in phone_candidates:
                            cleaned = re.sub(r'\D', '', candidate)
                            if cleaned.startswith('380') and len(cleaned) >= 11:
                                all_phones.append(candidate.strip())
                                logging.info(f"✅ Найден телефон через HTML-парсинг: {candidate.strip()}")
                    except Exception as e:
                        logging.info(f"Не удалось распарсить HTML для телефонов: {e}")
                        err_l = str(e).lower()
                        if any(m in err_l for m in _FATAL_BROWSER_ERROR_MARKERS):
                            logging.warning(
                                "💥 tab crash при HTML-парсинге — reconnect и skip объявления %s",
                                info.get("id"),
                            )
                            try:
                                _safe_quit_driver(driver)
                            except Exception:
                                pass
                            driver = None
                            reconnect_driver(notify=False)
                            ads_without_phones += 1
                            continue

                logging.info("📞 Итого найдено телефонов для %s: %d — %s",
                             info.get('id'), len(all_phones), all_phones if all_phones else "пусто")

                # Нормализуем: Auto.RIA часто склеивает несколько номеров в одну строку
                from spam_sms import extract_clean_phones

                all_phones = extract_clean_phones(all_phones)
                logging.info(
                    "📱 После нормализации для %s: %s",
                    info.get("id"),
                    all_phones if all_phones else "пусто",
                )

                if not all_phones:
                    ads_without_phones += 1
                    logging.warning(
                        "📞 Телефоны не получены для %s — не сохраняем, повторим в следующей итерации",
                        info.get('id'),
                    )
                    _count_and_maybe_restart_chrome()
                    continue

                ads_with_phones += 1

                if debug_mode:
                    logging.info(
                        "🧪 DEBUG: объявление %s найдено — в БД не сохраняем",
                        info.get("id"),
                    )

                # SMS сначала; в статистику AutoInfo — только после успешной отправки
                sms_outcome = None
                if all_phones:
                    sms_outcome = _try_ad_sms(
                        info=info,
                        link=link,
                        all_phones=all_phones,
                        debug_mode=debug_mode,
                        daily_limit_state=daily_limit_state,
                        _await=_await,
                        _notify_user=_notify_user,
                        user_id=user_id,
                        work_from=(filter_settings or {}).get("work_from"),
                        work_to=(filter_settings or {}).get("work_to"),
                        work_days=(filter_settings or {}).get("work_days"),
                    )
                    if sms_outcome == "sent" and not debug_mode:
                        _persist_autoinfo_after_sms_ok(info, link, all_phones, _await)
                else:
                    ads_without_phones += 1
                    logging.info(f"Объявление {info.get('id')} - телефоны не найдены")
                     
                logging.info(f"Объявление {info.get('id')} обработано")
                # Chrome часто оставляет <defunct> после tab crash — регулярно собираем zombie
                reaped = _reap_zombie_children()
                if reaped:
                    logging.info("🧹 После объявления собрано zombie: %s", reaped)

                # Периодический рестарт Chrome — против утечек памяти / tab crashed
                _count_and_maybe_restart_chrome()

                _pause_after_ad_outcome(sms_outcome, stop_event)

            except Exception as e:
                logging.error(f"Ошибка при обработке объявления: {e}")
                if driver:
                    try:
                        _safe_quit_driver(driver)
                    except Exception:
                        pass
                    driver = None
                else:
                    _reap_zombie_children()
                if stop_event.is_set():
                    logging.info("🛑 Парсинг остановлен — выходим после ошибки объявления.")
                    break
        
        # Обновляем общие счетчики
        total_processed_ads += processed_ads
        total_ads_with_phones += ads_with_phones
        total_ads_without_phones += ads_without_phones
        
        # Логируем статистику текущей итерации
        logging.info(f"✅ Итерация #{iteration} завершена:")
        logging.info(f"   Обработано объявлений: {processed_ads}")
        logging.info(f"   С телефонами: {ads_with_phones}")
        logging.info(f"   Без телефонов: {ads_without_phones}")
        logging.info(f"   Всего обработано за все итерации: {total_processed_ads}")
        _notify_user(
            f"📊 Итерация #{iteration} завершена\n"
            f"Обработано: {processed_ads}\n"
            f"С телефонами: {ads_with_phones}\n"
            f"Без телефонов: {ads_without_phones}"
        )
        
        # Проверяем, не остановил ли пользователь парсинг перед паузой
        if stop_event.is_set():
            logging.info("🛑 Парсинг остановлен пользователем перед паузой.")
            break
        
        # Закрываем браузер перед паузой и убираем zombie
        if driver:
            try:
                _safe_quit_driver(driver)
                logging.info("✅ Браузер закрыт перед паузой")
            except Exception as e:
                logging.warning(f"⚠️ Ошибка при закрытии браузера: {e}")
            driver = None
        else:
            _reap_zombie_children()

        # Пауза между итерациями (в DEBUG — короче)
        pause_time = random.randint(pause_iter_min, pause_iter_max)
        if pause_time >= 60:
            pause_minutes = pause_time // 60
            logging.info(f"😴 Сплю до следующего парсинга ({pause_minutes} мин)...")
        else:
            logging.info(f"😴 Сплю до следующего парсинга ({pause_time} с)...")
        logging.info(f"📊 Общая статистика после {iteration} итераций:")
        logging.info(f"   Всего обработано объявлений: {total_processed_ads}")
        logging.info(f"   С телефонами: {total_ads_with_phones}")
        logging.info(f"   Без телефонов: {total_ads_without_phones}")
        
        # Ожидание с проверкой stop_event каждую секунду
        for _ in range(pause_time):
            if stop_event.is_set():
                logging.info("🛑 Парсинг остановлен пользователем во время паузы.")
                break
            time.sleep(1)
        
    # Получаем статистику перед финализацией (только когда парсинг остановлен)
    try:
        current_success, current_failed = _await(get_totals())
        logging.info(f"Статистика парсинга: {current_success} успешных, {current_failed} неуспешных SMS")
    except Exception as e:
        logging.error(f"Ошибка при получении статистики: {e}")
        current_success, current_failed = 0, 0

    # Финализируем счетчики - переносим локальные значения в глобальные итоги
    try:
        moved_success, moved_failed = _await(finalize_counters())
        logging.info(f"Финализация: перенесено {moved_success} успешных, {moved_failed} неуспешных SMS")
    except Exception as e:
        logging.error(f"Ошибка при финализации счетчиков: {e}")

    # Отправляем статистику пользователю в Telegram
    if bot and user_id:
        try:
            # Проверяем, был ли парсинг остановлен
            status_text = "🛑 <b>Парсинг остановлен пользователем!</b>" if stop_event.is_set() else "🎯 <b>Парсинг завершен!</b>"
            
            stats_message = f"""{status_text}

🔄 <b>Всего итераций:</b> {iteration}

🏠 <b>Обработано объявлений (всего):</b> {total_processed_ads}
📞 <b>С телефонами:</b> {total_ads_with_phones}
🚫 <b>Без телефонов:</b> {total_ads_without_phones}

📊 <b>Статистика SMS за этот парсинг:</b>
✅ <b>Отправлено:</b> {current_success}
❌ <b>Не отправлено:</b> {current_failed}
📋 <b>Всего попыток:</b> {current_success + current_failed}

📈 <b>Процент успеха SMS:</b> {(current_success / (current_success + current_failed) * 100) if (current_success + current_failed) > 0 else 0:.1f}%
🎯 <b>Процент объявлений с телефонами:</b> {(total_ads_with_phones / total_processed_ads * 100) if total_processed_ads > 0 else 0:.1f}%

🏠 Используйте /start для возврата в главное меню."""

            # Отправляем сообщение асинхронно
            if main_loop and not main_loop.is_closed():
                asyncio.run_coroutine_threadsafe(
                    bot.send_message(
                        chat_id=user_id,
                        text=stats_message
                    ),
                    main_loop
                )
                logging.info(f"Статистика отправлена пользователю {user_id}")
            else:
                logging.error("Main loop недоступен, не могу отправить статистику")
        except Exception as e:
            logging.error(f"Ошибка при отправке статистики пользователю {user_id}: {e}")

    # Закрываем драйвер и ВСЕГДА очищаем процессы (раньше cleanup был только в except)
    try:
        if driver:
            _safe_quit_driver(driver)
            logging.info("✅ ChromeDriver корректно закрыт")
        else:
            _force_kill_chrome_processes()
    except Exception as e:
        logging.warning(f"⚠️ Ошибка при закрытии драйвера: {e}")
        _force_kill_chrome_processes()
    finally:
        reaped = _reap_zombie_children()
        if reaped:
            logging.info("🧹 Финально собрано zombie-процессов: %s", reaped)
        logging.info("Фоновый парсинг завершен")
