"""
سكريبت فحص الجلسات الميتة وحذفها من القاعدة.
يفحص كل حساب بالاتصال به، ويحذف الميت من قاعدة البيانات.
الاستخدام: python3 check_dead_sessions.py
"""
import asyncio
import os
import sys
import logging
import time
import psycopg2

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s | %(levelname)s | %(message)s"
)
log = logging.getLogger(__name__)

sys.path.insert(0, "/var/www/kodo")
import config
from pyrogram import Client
from pyrogram.errors import (
    AuthKeyUnregistered, AuthKeyDuplicated, SessionRevoked,
    SessionExpired, UserDeactivated, UserDeactivatedBan,
    FloodWait
)

# ─── إعدادات ───────────────────────────────────────────────────
SESSIONS_DIR = "/var/www/kodo/sessions"
PG_DSN       = "postgresql://mahde:MahdeFab1YssR@localhost/mahde"
CONCURRENCY  = 20      # عدد الحسابات المفحوصة بالتوازي
TIMEOUT      = 20      # مهلة الاتصال بالحساب الواحد
DRY_RUN      = False   # True = فحص فقط بدون حذف

# ─── أخطاء تدل على جلسة ميتة نهائياً ──────────────────────────
DEAD_ERRORS = (
    AuthKeyUnregistered,
    AuthKeyDuplicated,
    SessionRevoked,
    SessionExpired,
    UserDeactivated,
    UserDeactivatedBan,
)
DEAD_KEYWORDS = (
    "auth_key", "unregistered", "revoked", "session_expired",
    "malformed", "disk image", "eof when reading", "deactivated",
    "user_deactivated",
)

# ─── إحصاءات ───────────────────────────────────────────────────
stats = {
    "total": 0, "alive": 0, "dead": 0,
    "no_session_file": 0, "timeout": 0, "deleted": 0,
}
dead_list   = []
alive_list  = []

async def check_one(phone: str, sem: asyncio.Semaphore) -> str:
    """يفحص حساباً واحداً ويعيد: 'alive' | 'dead' | 'timeout' | 'no_file'"""
    session_path = os.path.join(SESSIONS_DIR, f"{phone}.session")
    if not os.path.exists(session_path):
        log.warning(f"[NO FILE] {phone}")
        return "no_file"

    async with sem:
        c = None
        try:
            c = Client(
                f"{SESSIONS_DIR}/{phone}",
                api_id=config.API_ID,
                api_hash=config.API_HASH,
                no_updates=True,
            )
            await asyncio.wait_for(c.connect(), timeout=TIMEOUT)
            await asyncio.wait_for(c.get_me(), timeout=TIMEOUT)
            log.info(f"[ALIVE] {phone}")
            return "alive"

        except DEAD_ERRORS as e:
            log.warning(f"[DEAD] {phone}: {type(e).__name__}")
            return "dead"

        except asyncio.TimeoutError:
            log.warning(f"[TIMEOUT] {phone}")
            return "timeout"

        except Exception as e:
            err = str(e).lower()
            if any(k in err for k in DEAD_KEYWORDS):
                log.warning(f"[DEAD] {phone}: {e}")
                return "dead"
            log.warning(f"[UNKNOWN] {phone}: {type(e).__name__}: {e}")
            return "alive"  # نتعامل معه كحي في حال الشك

        finally:
            if c is not None:
                try:
                    await asyncio.wait_for(c.disconnect(), timeout=5)
                except Exception:
                    pass


def get_accounts_from_db() -> list:
    """جلب الحسابات النشطة من قاعدة البيانات."""
    con = psycopg2.connect(PG_DSN)
    cur = con.cursor()
    cur.execute("SELECT phone FROM accounts WHERE banned=0 ORDER BY phone")
    rows = cur.fetchall()
    cur.close()
    con.close()
    return [r[0] for r in rows]


def delete_dead_from_db(phones: list):
    """حذف الجلسات الميتة من قاعدة البيانات (تعيين banned=1)."""
    if not phones:
        return
    con = psycopg2.connect(PG_DSN)
    cur = con.cursor()
    for phone in phones:
        cur.execute(
            "UPDATE accounts SET banned=1 WHERE phone=%s",
            (phone,)
        )
        log.info(f"[DB] تم تعطيل الحساب: {phone}")
    con.commit()
    cur.close()
    con.close()


async def main():
    print("=" * 60)
    print("🔍 سكريبت فحص الجلسات الميتة")
    if DRY_RUN:
        print("⚠️  وضع DRY RUN — لن يتم حذف أي شيء")
    print("=" * 60)

    phones = get_accounts_from_db()
    stats["total"] = len(phones)
    print(f"📊 إجمالي الحسابات النشطة في القاعدة: {stats['total']}")
    print(f"⚡ التزامن: {CONCURRENCY} حساب في نفس الوقت")
    print(f"⏱️  مهلة كل حساب: {TIMEOUT} ثانية")
    print("-" * 60)

    sem = asyncio.Semaphore(CONCURRENCY)
    start = time.time()

    tasks = [check_one(p, sem) for p in phones]
    results = await asyncio.gather(*tasks, return_exceptions=True)

    for phone, result in zip(phones, results):
        if isinstance(result, Exception):
            result = "alive"  # في حال الشك نعدّه حياً
        if result == "alive":
            stats["alive"] += 1
            alive_list.append(phone)
        elif result == "dead":
            stats["dead"] += 1
            dead_list.append(phone)
        elif result == "timeout":
            stats["timeout"] += 1
        elif result == "no_file":
            stats["no_session_file"] += 1

    elapsed = time.time() - start

    print("=" * 60)
    print(f"✅ اكتمل الفحص في {elapsed:.1f} ثانية")
    print(f"📊 الإجمالي      : {stats['total']}")
    print(f"💚 حي            : {stats['alive']}")
    print(f"💀 ميت           : {stats['dead']}")
    print(f"⏱️  تجاوز المهلة  : {stats['timeout']}")
    print(f"📁 لا يوجد ملف   : {stats['no_session_file']}")
    print("-" * 60)

    if dead_list:
        print(f"\n💀 الحسابات الميتة ({len(dead_list)}):")
        for p in dead_list:
            print(f"   • {p}")

        if not DRY_RUN:
            print("\n⏳ جاري تعطيل الحسابات الميتة في القاعدة...")
            delete_dead_from_db(dead_list)
            stats["deleted"] = len(dead_list)
            print(f"✅ تم تعطيل {stats['deleted']} حساب ميت في القاعدة (banned=1)")
        else:
            print("\n⚠️  DRY RUN: لم يتم حذف أي شيء.")
    else:
        print("✅ لا توجد حسابات ميتة!")

    print("=" * 60)


if __name__ == "__main__":
    asyncio.run(main())
