import os
import logging
import re
import sys

from aiogram import Bot, Dispatcher
from aiogram.client.default import DefaultBotProperties

from dotenv import load_dotenv

load_dotenv()


def _normalize_bot_token(raw: str | None) -> str:
    if not raw:
        return ""
    t = raw.strip().replace("\r", "").replace("\n", "")
    t = t.lstrip("\ufeff")  # BOM из некоторых редакторов
    if (len(t) >= 2) and ((t[0] == t[-1] == '"') or (t[0] == t[-1] == "'")):
        t = t[1:-1].strip()
    return t


# На сервере должен быть файл /home/autoria/.env с корректным токеном от @BotFather
BOT_TOKEN = _normalize_bot_token(
    os.getenv("BOT_TOKEN") or os.getenv("TELEGRAM_BOT_TOKEN")
)
if not BOT_TOKEN:
    sys.stderr.write(
        "FATAL: задайте BOT_TOKEN в .env рядом с docker-compose.yml "
        "(или TELEGRAM_BOT_TOKEN).\n"
    )
    sys.exit(1)

if not re.fullmatch(r"\d+:[A-Za-z0-9_-]+", BOT_TOKEN):
    sys.stderr.write(
        "FATAL: BOT_TOKEN не похож на токен Telegram "
        "(ожидается вид: 123456789:AA... без пробелов и лишних символов).\n"
        "Проверьте /home/autoria/.env и перезапустите: docker-compose up -d\n"
    )
    sys.exit(1)


def _env_bool(name: str, default: bool = False) -> bool:
    v = (os.getenv(name) or "").strip().lower()
    if not v:
        return default
    return v in ("1", "true", "yes", "on")


# Тестовая SMS при старте сеанса парсинга (до Chrome / AutoRia). По умолчанию выключено.
PARSING_START_SMS_TEST_ENABLED = _env_bool("PARSING_START_SMS_TEST_ENABLED", default=False)
PARSING_START_SMS_TEST_NUMBER = (os.getenv("PARSING_START_SMS_TEST_NUMBER") or "").strip()
if (PARSING_START_SMS_TEST_NUMBER.startswith('"') and PARSING_START_SMS_TEST_NUMBER.endswith('"')) or (
    PARSING_START_SMS_TEST_NUMBER.startswith("'") and PARSING_START_SMS_TEST_NUMBER.endswith("'")
):
    PARSING_START_SMS_TEST_NUMBER = PARSING_START_SMS_TEST_NUMBER[1:-1].strip()
if not PARSING_START_SMS_TEST_NUMBER:
    PARSING_START_SMS_TEST_NUMBER = "+380 63 423 64 36"


def _env_int(name: str, default: int) -> int:
    v = (os.getenv(name) or "").strip()
    if not v:
        return default
    try:
        return int(v)
    except ValueError:
        return default


def _env_pause_range(min_name: str, max_name: str, default_min: int, default_max: int) -> tuple[int, int]:
    lo = max(0, _env_int(min_name, default_min))
    hi = max(0, _env_int(max_name, default_max))
    if lo > hi:
        lo, hi = hi, lo
    return lo, hi


# Пауза между объявлениями внутри одной итерации (секунды, случайно в диапазоне).
PARSING_PAUSE_BETWEEN_ADS_SEC_MIN, PARSING_PAUSE_BETWEEN_ADS_SEC_MAX = _env_pause_range(
    "PARSING_PAUSE_BETWEEN_ADS_SEC_MIN",
    "PARSING_PAUSE_BETWEEN_ADS_SEC_MAX",
    180,
    300,
)
# Пауза после обхода списка объявлений, перед следующей итерацией (секунды).
# Увеличено по умолчанию (20–45 мин), чтобы реже бить BFF/сайт между полными проходами.
PARSING_PAUSE_BETWEEN_ITERATIONS_SEC_MIN, PARSING_PAUSE_BETWEEN_ITERATIONS_SEC_MAX = _env_pause_range(
    "PARSING_PAUSE_BETWEEN_ITERATIONS_SEC_MIN",
    "PARSING_PAUSE_BETWEEN_ITERATIONS_SEC_MAX",
    1200,
    2700,
)

# После успешной отправки SMS — пауза перед следующим объявлением (секунды, случайно min–max).
# По умолчанию 300–1200 (5–20 мин). SMS_PAUSE_* = 0 и 0 — только обычная пауза между объявлениями.
SMS_PAUSE_AFTER_SEND_SEC_MIN, SMS_PAUSE_AFTER_SEND_SEC_MAX = _env_pause_range(
    "SMS_PAUSE_AFTER_SEND_SEC_MIN",
    "SMS_PAUSE_AFTER_SEND_SEC_MAX",
    300,
    1200,
)

# Короткая пауза, если SMS не ушла (дубль / cooldown / нет валидного номера).
# Чтобы не жечь 1–2 мин на каждом уже обработанном номере после Chrome.
PARSING_PAUSE_ON_SKIP_SEC_MIN, PARSING_PAUSE_ON_SKIP_SEC_MAX = _env_pause_range(
    "PARSING_PAUSE_ON_SKIP_SEC_MIN",
    "PARSING_PAUSE_ON_SKIP_SEC_MAX",
    3,
    8,
)

# Сколько страниц выдачи Auto.RIA брать за одну итерацию (каждая ~20 объявлений).
# 5 страниц ≈ до ~100 уникальных объявлений.
PARSING_SEARCH_PAGES = max(1, _env_int("PARSING_SEARCH_PAGES", 5))

# Глубина «свежести» Auto.RIA: republished_last (дней).
# Типично: 1 / 2 / 7; 0 — без фильтра свежести (максимум объёма).
PARSING_REPUBLISHED_LAST = max(0, _env_int("PARSING_REPUBLISHED_LAST", 7))

# Пропускать объявления newauto (салоны / новые авто) — другой UI, нет кнопки телефона.
# true — не открывать такие ссылки в Chrome; false — обрабатывать как обычно.
PARSING_SKIP_NEWAUTO = _env_bool("PARSING_SKIP_NEWAUTO", default=True)

# Пропускать объявление, если на номер уже была успешная SMS в БД (true/false).
# true — никогда не слать повторно на такой номер;
# false — смотреть SMS_RESEND_AFTER_DAYS (cooldown в днях).
SMS_SKIP_ALREADY_SENT = _env_bool("SMS_SKIP_ALREADY_SENT", default=True)

# Повторная SMS на тот же номер (если SMS_SKIP_ALREADY_SENT=false):
# 0 — никогда не отправлять повторно;
# N>0 — пропускать, если успешная отправка была меньше N дней назад.
SMS_RESEND_AFTER_DAYS = max(0, _env_int("SMS_RESEND_AFTER_DAYS", 7))


def effective_sms_resend_after_days() -> int:
    """Дней cooldown для антидубля: при SMS_SKIP_ALREADY_SENT всегда 0 (запрет повтора)."""
    if SMS_SKIP_ALREADY_SENT:
        return 0
    return SMS_RESEND_AFTER_DAYS

# Перезапуск Chrome каждые N объявлений, реально открытых в браузере (0 = не перезапускать).
# Чаще = меньше tab crashed из‑за утечек (рекомендуется 8–10 на VPS).
CHROME_RESTART_EVERY_ADS = max(0, _env_int("CHROME_RESTART_EVERY_ADS", 8))

# Таймауты Selenium (секунды) — укорочены для стабильности/скорости.
PARSING_PAGE_WAIT_SEC = max(1, _env_int("PARSING_PAGE_WAIT_SEC", 10))
PARSING_AFTER_CLICK_WAIT_SEC = max(1, _env_int("PARSING_AFTER_CLICK_WAIT_SEC", 8))
PARSING_PHONE_FIND_WAIT_SEC = max(1, _env_int("PARSING_PHONE_FIND_WAIT_SEC", 6))
PARSING_RECONNECT_WAIT_SEC = max(1, _env_int("PARSING_RECONNECT_WAIT_SEC", 10))
PARSING_SCROLL_WAIT_SEC = max(0, _env_int("PARSING_SCROLL_WAIT_SEC", 2))
PARSING_STAGE_PAUSE_SEC = max(0, _env_int("PARSING_STAGE_PAUSE_SEC", 1))

# Фоновый контроль автоработы по расписанию (секунды между проверками)
SCHEDULE_TICK_SEC = max(15, _env_int("SCHEDULE_TICK_SEC", 30))

# Дневной лимит успешных SMS: 0 = без лимита
SMS_DAILY_LIMIT = max(0, _env_int("SMS_DAILY_LIMIT", 0))

# Час автоотправки дневного отчёта (0–23) по TZ. -1 = только вручную
DAILY_REPORT_HOUR = _env_int("DAILY_REPORT_HOUR", 20)
if DAILY_REPORT_HOUR < -1 or DAILY_REPORT_HOUR > 23:
    DAILY_REPORT_HOUR = 20


def _env_user_ids(name: str) -> frozenset[int]:
    """CSV telegram user_id → frozenset. Пустая строка → пустой набор."""
    raw = (os.getenv(name) or "").strip()
    if not raw:
        return frozenset()
    out: set[int] = set()
    for part in raw.replace(";", ",").split(","):
        part = part.strip()
        if not part:
            continue
        try:
            uid = int(part)
        except ValueError:
            continue
        if uid > 0:
            out.add(uid)
    return frozenset(out)


# Админы: CSV в ADMIN_USER_ID (например 111,222). Пусто = не слать админам.
ADMIN_USER_IDS = _env_user_ids("ADMIN_USER_ID")
# Первый ID — для обратной совместимости (старый код с одним int)
ADMIN_USER_ID = next(iter(sorted(ADMIN_USER_IDS)), 0)

# Дублировать критичные алерты админу
ERROR_ALERTS_TO_ADMIN = _env_bool("ERROR_ALERTS_TO_ADMIN", default=True)

# Whitelist: пусто = доступ открыт; непусто = только эти ID + все ADMIN_USER_IDS
ALLOWED_USER_IDS = _env_user_ids("ALLOWED_USER_IDS")


def is_user_allowed(user_id: int | None) -> bool:
    """True, если пользователю разрешено пользоваться ботом."""
    if not user_id:
        return False
    uid = int(user_id)
    if uid in ADMIN_USER_IDS:
        return True
    if not ALLOWED_USER_IDS:
        return True
    return uid in ALLOWED_USER_IDS


# Авто-рестарт parsing task после падения (не путать с reconnect Chrome внутри сессии)
PARSE_AUTO_RESTART = _env_bool("PARSE_AUTO_RESTART", default=True)
PARSE_AUTO_RESTART_DELAY_SEC = max(5, _env_int("PARSE_AUTO_RESTART_DELAY_SEC", 30))
PARSE_AUTO_RESTART_MAX = max(0, _env_int("PARSE_AUTO_RESTART_MAX", 5))

# Периодический статус админу в Telegram (минуты). 0 = выкл
ADMIN_STATS_INTERVAL_MIN = max(0, _env_int("ADMIN_STATS_INTERVAL_MIN", 60))

# Тик flush отложенных SMS (секунды)
PENDING_SMS_TICK_SEC = max(15, _env_int("PENDING_SMS_TICK_SEC", 30))

# Пауза между SMS из очереди pending (секунды). Короче, чем SMS_PAUSE_* при парсинге,
# чтобы слить накопленную очередь быстрее. 0 и 0 — без паузы.
PENDING_SMS_PAUSE_AFTER_SEND_SEC_MIN, PENDING_SMS_PAUSE_AFTER_SEND_SEC_MAX = _env_pause_range(
    "PENDING_SMS_PAUSE_AFTER_SEND_SEC_MIN",
    "PENDING_SMS_PAUSE_AFTER_SEND_SEC_MAX",
    3,
    8,
)


# Создаем папку для логов, если её нет
LOGS_DIR = "logs"
os.makedirs(LOGS_DIR, exist_ok=True)

_log_handlers = [logging.StreamHandler()]
try:
    _log_handlers.insert(
        0,
        logging.FileHandler(os.path.join(LOGS_DIR, "bot.log"), encoding="utf-8"),
    )
except OSError as e:
    sys.stderr.write(f"Предупреждение: не удалось открыть logs/bot.log ({e}), только консоль.\n")

# Настройка базового логирования
logging.basicConfig(
    level=logging.INFO,  # Уровень логов: DEBUG, INFO, WARNING, ERROR, CRITICAL
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",  # Формат вывода
    handlers=_log_handlers,
)

# Пример получения логгера в других модулях
logger = logging.getLogger(__name__)
logger.info("Логирование настроено. Логи сохраняются в 'logs/bot.log'.")


# Создаем объект бота
bot = Bot(token=BOT_TOKEN, default=DefaultBotProperties(parse_mode="HTML"))

# Создаем диспетчер
dp = Dispatcher()
