"""Большой аудит VakHunter: мульти-подписки, редкие специальности,
уведомления (дедуп/тихие часы/гейты), оплаты (Platega webhook).
Всё на проде с бэкапом и полным откатом тестовых данных."""
import asyncio
import json
import os
import shutil
import sqlite3
import sys
import time
import urllib.request

os.chdir(os.path.expanduser("~/Services/VakHunter"))
sys.path.insert(0, ".")

UID = 8688355181  # тест-юзер
DB = "vakhunter.db"
PASS, FAIL = [], []


def check(name, ok, detail=""):
    (PASS if ok else FAIL).append(name)
    print(("✅" if ok else "❌"), name, ("— " + str(detail)[:160]) if detail else "")


# ── бэкап ──
bak = f"{DB}.bak-pre-audit-{int(time.time())}"
shutil.copy(DB, bak)
print("бэкап:", bak)

from database.db import Database  # noqa: E402
db = Database()

conn = sqlite3.connect(DB)
conn.row_factory = sqlite3.Row
plans = [dict(r) for r in conn.execute("SELECT id,name,max_subscriptions,allowed_sources FROM tariff_plans ORDER BY id")]
print("тарифы:", [(p["id"], p["name"], p["max_subscriptions"]) for p in plans])
pro = next((p for p in plans if "pro" in p["name"].lower()), plans[-1])
orig_user = dict(conn.execute("SELECT tariff_id, tariff_expires, employment_type FROM users WHERE user_id=?", (UID,)).fetchone())
conn.close()

# ═══ 1. МУЛЬТИ-ПОДПИСКИ НА ОДНОМ ПРОФИЛЕ ═══
print("\n═══ 1. Мульти-подписки (один профиль, разные специфики) ═══")
db.update_tariff(UID, pro["id"], None)  # Pro → много подписок

sub_ids = []
sub_ids.append(db.add_subscription(UID, specialty="python разработчик", city="Москва"))
sub_ids.append(db.add_subscription(UID, specialty="сварщик", city="Екатеринбург"))
sub_ids.append(db.add_subscription(UID, specialty="повар", city="Москва"))
db.update_subscription_filters(sub_ids[0], employment_type="permanent")
db.update_subscription_filters(sub_ids[1], employment_type="any")
db.update_subscription_filters(sub_ids[2], employment_type="part_time", salary_min=40000)
check("создано 3 подписки разных специфик", all(sub_ids), sub_ids)

subs_db = db.get_user_subscriptions(UID)
active_now = [s for s in subs_db if s.get("is_active")]
check("подписки видны через get_user_subscriptions", len(active_now) >= 3, f"active={len(active_now)}")

# лимит подписок: на Trial должен быть отказ на 2-й
limits_pro = db.get_active_subscriptions()
check("get_active_subscriptions включает все 3", sum(1 for s in limits_pro if s["user_id"] == UID) >= 3)

# ═══ 2. ПОЛНЫЙ ЦИКЛ ПОДПИСКИ через scheduler (реальные парсеры + реальная отправка в TG) ═══
print("\n═══ 2. Полный цикл scheduler (парсинг → фильтр → отправка) ═══")
from telegram import Bot  # noqa: E402
from telegram.request import HTTPXRequest  # noqa: E402
from bot.config import config as cfg  # noqa: E402
from bot.scheduler import VakHunterScheduler  # noqa: E402

token = getattr(cfg, "bot_token", None) or getattr(cfg, "telegram_token", None) or os.getenv("BOT_TOKEN") or os.getenv("TELEGRAM_BOT_TOKEN")
req = HTTPXRequest(proxy="http://127.0.0.1:2081", connection_pool_size=8)
bot = Bot(token, request=req)
sched = VakHunterScheduler(bot)


async def run_cycle():
    # 2a. тихие часы: сейчас ~23:30 МСК → _process_subscription обязан скипнуть
    conn = sqlite3.connect(DB)
    before = conn.execute("SELECT COUNT(*) FROM sent_vacancies WHERE user_id=?", (UID,)).fetchone()[0]
    conn.close()
    sub0 = db.get_subscription(sub_ids[0])
    import pytz
    from datetime import datetime
    msk_hour = datetime.now(pytz.timezone("Europe/Moscow")).hour
    await sched._process_subscription(sub0)
    conn = sqlite3.connect(DB)
    after = conn.execute("SELECT COUNT(*) FROM sent_vacancies WHERE user_id=?", (UID,)).fetchone()[0]
    conn.close()
    in_quiet = not (9 <= msk_hour < 21)
    if in_quiet:
        check("тихие часы: в 21-9 МСК подписка НЕ шлёт", after == before, f"msk_hour={msk_hour} sent {before}->{after}")
    else:
        print(f"ℹ️ сейчас {msk_hour}ч МСК — дневное окно, тихие часы проверим настройкой")

    # 2b. расширяем окно юзеру и гоним все 3 подписки полным циклом
    conn = sqlite3.connect(DB)
    cols = [r[1] for r in conn.execute("PRAGMA table_info(notification_settings)")]
    row = conn.execute("SELECT user_id FROM notification_settings WHERE user_id=?", (UID,)).fetchone()
    if row:
        conn.execute("UPDATE notification_settings SET start_hour=0, end_hour=24 WHERE user_id=?", (UID,))
    else:
        conn.execute("INSERT INTO notification_settings (user_id, start_hour, end_hour) VALUES (?,0,24)", (UID,))
    conn.commit(); conn.close()

    for sid, tag in zip(sub_ids, ["python/Мск/полная", "сварщик/Екб/любая", "повар/Мск/частичная+40к"]):
        sub = db.get_subscription(sid)
        t0 = time.time()
        try:
            await sched._process_subscription(sub)
            conn = sqlite3.connect(DB)
            cnt = conn.execute(
                "SELECT COUNT(*) FROM sent_vacancies WHERE user_id=? AND sent_at > datetime('now','-3 minute')",
                (UID,)).fetchone()[0]
            conn.close()
            print(f"   [{tag}] цикл {time.time()-t0:.0f}с, sent_vacancies(3мин)={cnt}")
        except Exception as e:
            check(f"цикл подписки [{tag}] без исключений", False, e)
    conn = sqlite3.connect(DB)
    total_new = conn.execute(
        "SELECT COUNT(*) FROM sent_vacancies WHERE user_id=? AND sent_at > datetime('now','-10 minute')", (UID,)).fetchone()[0]
    lc = conn.execute("SELECT COUNT(*) FROM subscriptions WHERE id IN (%s) AND last_check IS NOT NULL" % ",".join("?"*len(sub_ids)), sub_ids).fetchone()[0]
    conn.close()
    check("все 3 подписки обработаны (last_check обновлён)", lc == 3, f"{lc}/3")
    check("реальная доставка в TG прошла (sent_vacancies выросли)", total_new > 0, f"+{total_new} за прогон")

    # 2c. ДЕДУП: повторный прогон той же подписки не должен слать повторно те же вакансии
    conn = sqlite3.connect(DB)
    before2 = conn.execute("SELECT COUNT(*) FROM sent_vacancies WHERE user_id=?", (UID,)).fetchone()[0]
    conn.close()
    await sched._process_subscription(db.get_subscription(sub_ids[0]))
    conn = sqlite3.connect(DB)
    after2 = conn.execute("SELECT COUNT(*) FROM sent_vacancies WHERE user_id=?", (UID,)).fetchone()[0]
    dup = conn.execute("""SELECT COUNT(*) FROM (SELECT vacancy_id, COUNT(*) c FROM sent_vacancies
                          WHERE user_id=? GROUP BY vacancy_id HAVING c>1)""", (UID,)).fetchone()[0]
    conn.close()
    check("дедуп: нет повторных vacancy_id в sent_vacancies", dup == 0, f"дублей={dup}, повторный прогон +{after2-before2} (новые с площадок — ок)")

asyncio.run(run_cycle())

# ═══ 3. РЕДКИЕ НЕ-IT СПЕЦИАЛЬНОСТИ (прямые вызовы парсеров + фильтр) ═══
print("\n═══ 3. Редкие специальности (прямые парсеры) ═══")
from bot.scheduler import PARSERS  # noqa: E402
from bot.vacancy_filters import filter_vacancies  # noqa: E402

RARE = ["логопед", "машинист крана", "ветеринар", "токарь"]
UNIVERSAL = ["trudvsem", "gorodrabot", "hh", "zarplata"]


async def rare_run():
    for spec in RARE:
        total, kept = 0, 0
        per_src = {}
        for src in UNIVERSAL:
            if src not in PARSERS:
                continue
            try:
                res = await PARSERS[src].search(query=spec, city="", limit=5)
            except Exception:
                res = []
            per_src[src] = len(res or [])
            total += len(res or [])
            f = filter_vacancies(res or [], "", "any", spec, "")
            kept += len(f)
        check(f"«{spec}»: площадки отдают вакансии", total > 0, f"raw={per_src}")
        if total:
            check(f"«{spec}»: релевантность-фильтр не выкашивает всё", kept > 0, f"после фильтра {kept}/{total}")

asyncio.run(rare_run())

# ═══ 4. УВЕДОМЛЕНИЯ: гейты ═══
print("\n═══ 4. Уведомления: гейты и opt-in ═══")
rt_users = db.get_realtime_alert_users()
conn = sqlite3.connect(DB)
trial_in_rt = 0
for u in rt_users:
    r = conn.execute("SELECT tariff_id FROM users WHERE user_id=?", (u["user_id"],)).fetchone()
    if r and r[0] == 1:
        trial_in_rt += 1
digest_on = conn.execute("SELECT COUNT(*) FROM notification_settings WHERE weekly_digest=1").fetchone()[0] \
    if "weekly_digest" in [r[1] for r in conn.execute("PRAGMA table_info(notification_settings)")] else "n/a"
conn.close()
check("realtime только Pro/Premium (Trial не пролез)", trial_in_rt == 0, f"rt_users={len(rt_users)}, trial из них={trial_in_rt}")
print(f"ℹ️ weekly_digest включён у: {digest_on} (opt-in, дефолт выкл)")

# ═══ 5. ОПЛАТЫ: Platega webhook (реальный HTTP на :8080) ═══
print("\n═══ 5. Оплаты (webhook :8080) ═══")
env = {}
for ln in open(".env", encoding="utf-8"):
    ln = ln.strip()
    if "=" in ln and not ln.startswith("#"):
        k, v = ln.split("=", 1)
        env[k.strip()] = v.strip().strip('"').strip("'")
MID, SEC = env.get("PLATEGA_MERCHANT_ID", ""), env.get("PLATEGA_SECRET", "")
PORT = env.get("PAYMENT_WEBHOOK_PORT", "8080")
URL = f"http://127.0.0.1:{PORT}/payment/platega"


def hook(payload, mid=MID, sec=SEC):
    data = json.dumps(payload).encode()
    rq = urllib.request.Request(URL, data=data, headers={
        "Content-Type": "application/json", "X-MerchantId": mid, "X-Secret": sec})
    try:
        with urllib.request.urlopen(rq, timeout=15) as r:
            return r.status, r.read().decode()
    except urllib.error.HTTPError as e:
        return e.code, e.read().decode()


inv1, inv2 = f"AUDIT-{int(time.time())}-A", f"AUDIT-{int(time.time())}-B"
std = next((p for p in plans if "standard" in p["name"].lower() or "стандарт" in p["name"].lower()), plans[1])

# 5a. неверный секрет → 403 (fail-closed)
st, _ = hook({"id": inv1, "status": "CONFIRMED", "amount": 149, "payload": f"{UID}:{std['id']}"}, sec="WRONG")
check("оплата: неверный X-Secret отбивается 403", st == 403, f"http={st}")

# 5b. CONFIRMED → тариф активирован (payload-фолбэк, строки payments не было)
st, body = hook({"id": inv1, "status": "CONFIRMED", "amount": 149, "payload": f"{UID}:{std['id']}"})
time.sleep(2)
conn = sqlite3.connect(DB)
prow = conn.execute("SELECT status, tariff_id FROM payments WHERE invoice_id=?", (inv1,)).fetchone()
urow = conn.execute("SELECT tariff_id, tariff_expires FROM users WHERE user_id=?", (UID,)).fetchone()
conn.close()
check("оплата: CONFIRMED → payments.succeeded", st == 200 and prow and prow[0] == "succeeded", f"http={st} row={prow}")
check("оплата: тариф применён с экспирацией +30д", urow and urow[0] == std["id"] and urow[1], f"user={urow}")
exp_before = urow[1] if urow else None

# 5c. идемпотентность: повторный CONFIRMED не двигает экспирацию
time.sleep(1)
hook({"id": inv1, "status": "CONFIRMED", "amount": 149, "payload": f"{UID}:{std['id']}"})
time.sleep(2)
conn = sqlite3.connect(DB)
urow2 = conn.execute("SELECT tariff_id, tariff_expires FROM users WHERE user_id=?", (UID,)).fetchone()
conn.close()
check("оплата: повторный webhook идемпотентен", urow2 and urow2[1] == exp_before, f"expires {exp_before} == {urow2[1] if urow2 else None}")

# 5d. CANCELED другой транзакции — тариф не трогает
hook({"id": inv2, "status": "CONFIRMED", "amount": 149, "payload": f"{UID}:{std['id']}"})
time.sleep(1)
st, _ = hook({"id": inv2, "status": "CANCELED"})
time.sleep(1)
conn = sqlite3.connect(DB)
p2 = conn.execute("SELECT status FROM payments WHERE invoice_id=?", (inv2,)).fetchone()
conn.close()
check("оплата: CANCELED помечает платёж", p2 and p2[0] == "canceled", f"row={p2}")

# 5e. CHARGEBACKED — возврат денег → сброс на Trial
st, _ = hook({"id": inv1, "status": "CHARGEBACKED"})
time.sleep(2)
conn = sqlite3.connect(DB)
p1 = conn.execute("SELECT status FROM payments WHERE invoice_id=?", (inv1,)).fetchone()
urow3 = conn.execute("SELECT tariff_id FROM users WHERE user_id=?", (UID,)).fetchone()
conn.close()
check("оплата: CHARGEBACKED → chargeback + сброс на Trial", p1 and p1[0] == "chargeback" and urow3 and urow3[0] == 1, f"pay={p1} tariff={urow3}")

# ═══ ОТКАТ тестовых данных ═══
print("\n═══ Откат ═══")
conn = sqlite3.connect(DB)
conn.execute("DELETE FROM payments WHERE invoice_id LIKE 'AUDIT-%'")
for sid in sub_ids:
    conn.execute("DELETE FROM sent_vacancies WHERE user_id=? AND sent_at > datetime('now','-30 minute')", (UID,))
    conn.execute("DELETE FROM subscriptions WHERE id=?", (sid,))
conn.execute("UPDATE notification_settings SET start_hour=9, end_hour=21 WHERE user_id=?", (UID,))
conn.execute("UPDATE users SET tariff_id=?, tariff_expires=?, employment_type=? WHERE user_id=?",
             (orig_user["tariff_id"], orig_user["tariff_expires"], orig_user["employment_type"], UID))
conn.commit()
left = conn.execute("SELECT COUNT(*) FROM subscriptions WHERE user_id=?", (UID,)).fetchone()[0]
urow = conn.execute("SELECT tariff_id FROM users WHERE user_id=?", (UID,)).fetchone()
conn.close()
print(f"откат: подписок тест-юзера осталось {left}, тариф возвращён к {urow[0]}")

print("\n" + "=" * 60)
print(f"ИТОГ: {len(PASS)} PASS / {len(FAIL)} FAIL")
for f in FAIL:
    print("  ❌", f)
print("=" * 60)
