#!/usr/bin/env python3
"""
🌐 وب‌سایت ورود خودکار به روبیکا - تک فایل
"""
import subprocess
import sys
import os

# -------------------- نصب خودکار پیش‌نیازها --------------------
def install_if_missing():
    packages = {
        "fastapi": "fastapi",
        "uvicorn": "uvicorn[standard]",
        "websockets": "websockets",
        "rubpy": "rubpy",
    }
    for module, pkg in packages.items():
        try:
            __import__(module)
        except ImportError:
            print(f"📦 نصب {pkg}...")
            try:
                subprocess.check_call([sys.executable, "-m", "pip", "install", pkg])
            except Exception as e:
                print(f"⚠️ خطا در نصب {pkg}: {e}")

install_if_missing()

import asyncio
import builtins
import queue
import threading
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse
import uvicorn

try:
    from rubpy import Client
    LIBRARY_NAME = "rubpy"
except ImportError:
    try:
        from pyshad import Client
        LIBRARY_NAME = "pyshad"
    except ImportError:
        print("❌ هیچ کتابخانه‌ای نصب نشد.")
        sys.exit(1)


# -------------------- HTML جاسازی‌شده --------------------
HTML_PAGE = """
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ورود خودکار به روبیکا</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }

body {
    font-family: 'Segoe UI', Tahoma, sans-serif;
    background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%);
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    color: #fff;
    direction: rtl;
    overflow-x: hidden;
}

body::before {
    content: '';
    position: fixed;
    inset: 0;
    background:
        radial-gradient(circle at 20% 20%, rgba(102,126,234,0.15), transparent 40%),
        radial-gradient(circle at 80% 80%, rgba(118,75,162,0.15), transparent 40%);
    pointer-events: none;
    z-index: 0;
}

.container {
    width: 100%;
    max-width: 520px;
    background: rgba(255, 255, 255, 0.06);
    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
    border-radius: 28px;
    border: 1px solid rgba(255, 255, 255, 0.1);
    box-shadow: 0 30px 60px rgba(0, 0, 0, 0.5);
    overflow: hidden;
    animation: fadeIn 0.6s ease;
    position: relative;
    z-index: 1;
}

@keyframes fadeIn {
    from { opacity: 0; transform: translateY(30px) scale(0.98); }
    to { opacity: 1; transform: translateY(0) scale(1); }
}

.header {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    padding: 32px 24px;
    text-align: center;
    position: relative;
    overflow: hidden;
}

.header::before {
    content: '';
    position: absolute;
    top: -50%; right: -50%;
    width: 200%; height: 200%;
    background: radial-gradient(circle, rgba(255,255,255,0.15) 0%, transparent 60%);
    animation: pulse 5s infinite;
}

@keyframes pulse {
    0%, 100% { transform: scale(1); opacity: 0.4; }
    50% { transform: scale(1.15); opacity: 0.7; }
}

.header h1 {
    font-size: 22px;
    font-weight: 700;
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
}

.logo {
    width: 42px; height: 42px;
    background: rgba(255,255,255,0.95);
    border-radius: 14px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 24px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.2);
    animation: float 3s ease-in-out infinite;
}

@keyframes float {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-4px); }
}

.header p {
    margin-top: 10px;
    opacity: 0.9;
    font-size: 13px;
    position: relative;
    z-index: 1;
}

.body { padding: 28px; }

.form-group {
    margin-bottom: 18px;
    animation: slideIn 0.4s ease;
}

@keyframes slideIn {
    from { opacity: 0; transform: translateX(15px); }
    to { opacity: 1; transform: translateX(0); }
}

label {
    display: block;
    margin-bottom: 8px;
    font-size: 13px;
    font-weight: 600;
    color: rgba(255,255,255,0.8);
}

input {
    width: 100%;
    padding: 15px 18px;
    background: rgba(255, 255, 255, 0.07);
    border: 2px solid rgba(255, 255, 255, 0.1);
    border-radius: 14px;
    color: #fff;
    font-size: 16px;
    transition: all 0.25s ease;
    direction: ltr;
    text-align: center;
    font-family: inherit;
}

input:focus {
    outline: none;
    border-color: #667eea;
    background: rgba(255, 255, 255, 0.12);
    box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.2);
}

input::placeholder { color: rgba(255, 255, 255, 0.35); }

button {
    width: 100%;
    padding: 15px 24px;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    border: none;
    border-radius: 14px;
    color: white;
    font-size: 15px;
    font-weight: 700;
    cursor: pointer;
    transition: all 0.25s ease;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    font-family: inherit;
    position: relative;
    overflow: hidden;
}

button::before {
    content: '';
    position: absolute;
    top: 0; left: -100%;
    width: 100%; height: 100%;
    background: linear-gradient(90deg, transparent, rgba(255,255,255,0.25), transparent);
    transition: left 0.5s;
}

button:hover:not(:disabled)::before { left: 100%; }

button:hover:not(:disabled) {
    transform: translateY(-2px);
    box-shadow: 0 12px 28px rgba(102, 126, 234, 0.45);
}

button:active:not(:disabled) { transform: translateY(0); }
button:disabled { opacity: 0.5; cursor: not-allowed; }

.btn-secondary {
    background: rgba(255, 255, 255, 0.1);
    border: 1px solid rgba(255, 255, 255, 0.15);
}

.btn-secondary:hover:not(:disabled) {
    background: rgba(255, 255, 255, 0.18);
}

.code-group {
    display: flex;
    gap: 10px;
}

.code-group input {
    flex: 1;
    letter-spacing: 10px;
    font-size: 22px;
    font-weight: 700;
}

.code-group button {
    width: auto;
    min-width: 60px;
    padding: 15px 20px;
    font-size: 20px;
}

.log {
    background: rgba(0, 0, 0, 0.35);
    border: 1px solid rgba(255,255,255,0.06);
    border-radius: 14px;
    padding: 16px;
    margin-top: 20px;
    max-height: 220px;
    overflow-y: auto;
    font-family: 'Consolas', monospace;
    font-size: 12.5px;
    line-height: 1.9;
}

.log-entry {
    padding: 5px 0;
    animation: slideIn 0.3s ease;
    border-bottom: 1px dashed rgba(255,255,255,0.05);
}
.log-entry:last-child { border-bottom: none; }

.user-info {
    background: linear-gradient(135deg, rgba(102,126,234,0.12), rgba(118,75,162,0.12));
    border: 1px solid rgba(102, 126, 234, 0.3);
    border-radius: 16px;
    padding: 20px;
    margin-top: 20px;
    animation: fadeIn 0.5s ease;
}

.user-info h3 {
    font-size: 15px;
    margin-bottom: 16px;
    display: flex;
    align-items: center;
    gap: 8px;
    color: #a5b4fc;
    border-bottom: 1px solid rgba(255,255,255,0.08);
    padding-bottom: 12px;
}

.info-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 10px 4px;
    border-bottom: 1px solid rgba(255,255,255,0.05);
    font-size: 13px;
    gap: 12px;
}
.info-row:last-child { border-bottom: none; }

.info-key {
    color: rgba(255,255,255,0.55);
    font-weight: 600;
    flex-shrink: 0;
    font-size: 12px;
}

.info-value {
    color: #fff;
    font-weight: 500;
    direction: ltr;
    word-break: break-all;
    text-align: left;
    max-width: 60%;
    font-family: 'Consolas', monospace;
    font-size: 12px;
}

.status {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 6px 14px;
    border-radius: 20px;
    font-size: 12px;
    font-weight: 700;
    margin-bottom: 20px;
}
.status.connected { background: rgba(34, 197, 94, 0.18); color: #4ade80; }
.status.waiting { background: rgba(251, 191, 36, 0.18); color: #fbbf24; }
.status.error { background: rgba(239, 68, 68, 0.18); color: #f87171; }

.dot {
    width: 8px; height: 8px;
    border-radius: 50%;
    background: currentColor;
    box-shadow: 0 0 8px currentColor;
    animation: blink 1.5s infinite;
}

@keyframes blink {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.4; }
}

.hidden { display: none !important; }

.log::-webkit-scrollbar { width: 6px; }
.log::-webkit-scrollbar-track { background: transparent; }
.log::-webkit-scrollbar-thumb {
    background: rgba(102,126,234,0.4);
    border-radius: 3px;
}

.footer {
    text-align: center;
    padding: 16px;
    font-size: 11px;
    color: rgba(255,255,255,0.3);
    border-top: 1px solid rgba(255,255,255,0.05);
}
</style>
</head>
<body>
<div class="container">
    <div class="header">
        <h1>
            <div class="logo">🤖</div>
            ورود خودکار به روبیکا
        </h1>
        <p>با شماره تلفن خود وارد شوید</p>
    </div>

    <div class="body">
        <div id="statusBar" class="status waiting">
            <span class="dot"></span>
            <span id="statusText">در حال اتصال...</span>
        </div>

        <!-- مرحله ۱: شماره تلفن -->
        <div id="step1">
            <div class="form-group">
                <label>📱 شماره تلفن (با کد کشور)</label>
                <input type="tel" id="phone" placeholder="+989123456789" autocomplete="tel">
            </div>
            <button id="startBtn" onclick="startLogin()">
                🚀 شروع ورود
            </button>
        </div>

        <!-- مرحله ۲: کد تأیید -->
        <div id="step2" class="hidden">
            <div class="form-group">
                <label>🔢 کد تأیید ارسال شده</label>
                <div class="code-group">
                    <input type="text" id="code" placeholder="12345" maxlength="10" autocomplete="one-time-code">
                    <button class="btn-secondary" onclick="sendCode()" title="ارسال">✓</button>
                </div>
            </div>
        </div>

        <!-- لاگ -->
        <div id="logBox" class="log hidden"></div>

        <!-- اطلاعات کاربر -->
        <div id="userInfoBox" class="user-info hidden">
            <h3>👤 اطلاعات حساب کاربری</h3>
            <div id="userInfoContent"></div>
        </div>
    </div>

    <div class="footer">
        🔒 اطلاعات شما به‌صورت محلی ذخیره می‌شود
    </div>
</div>

<script>
let ws = null;
let currentStatus = 'waiting';

function setStatus(text, type='waiting') {
    const bar = document.getElementById('statusBar');
    const txt = document.getElementById('statusText');
    bar.className = 'status ' + type;
    txt.textContent = text;
    currentStatus = type;
}

function addLog(msg) {
    const box = document.getElementById('logBox');
    box.classList.remove('hidden');
    const div = document.createElement('div');
    div.className = 'log-entry';
    div.textContent = msg;
    box.appendChild(div);
    box.scrollTop = box.scrollHeight;
}

function showUserInfo(data) {
    const box = document.getElementById('userInfoBox');
    const content = document.getElementById('userInfoContent');
    content.innerHTML = '';

    const keys = Object.keys(data);
    if (keys.length === 0) {
        content.innerHTML = '<div class="info-row">اطلاعاتی برای نمایش نیست</div>';
    } else {
        keys.forEach(k => {
            const row = document.createElement('div');
            row.className = 'info-row';
            const keyEl = document.createElement('div');
            keyEl.className = 'info-key';
            keyEl.textContent = k;
            const valEl = document.createElement('div');
            valEl.className = 'info-value';
            let v = data[k];
            if (typeof v === 'object') v = JSON.stringify(v);
            valEl.textContent = v == null ? '—' : String(v);
            row.appendChild(keyEl);
            row.appendChild(valEl);
            content.appendChild(row);
        });
    }
    box.classList.remove('hidden');
}

function connect() {
    ws = new WebSocket((location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host + '/ws');

    ws.onopen = () => {
        setStatus('متصل به سرور', 'connected');
    };

    ws.onmessage = (event) => {
        let data;
        try { data = JSON.parse(event.data); }
        catch(e) { return; }

        if (data.type === 'ready') {
            addLog('📚 کتابخانه: ' + data.library);
            addLog('✅ سرور آماده است');
        }
        else if (data.type === 'log') {
            addLog(data.message);
        }
        else if (data.type === 'input_required') {
            // نمایش فیلد کد تأیید
            document.getElementById('step2').classList.remove('hidden');
            document.getElementById('step1').classList.add('hidden');
            addLog('🔢 ' + (data.prompt || 'کد تأیید را وارد کنید'));
            setStatus('در انتظار کد تأیید...', 'waiting');
            document.getElementById('code').focus();
        }
        else if (data.type === 'user_info') {
            showUserInfo(data.data);
            addLog('📋 اطلاعات حساب دریافت شد');
        }
        else if (data.type === 'success') {
            setStatus('✅ ورود کامل شد', 'connected');
            addLog('🎉 ' + (data.message || 'عملیات با موفقیت انجام شد'));
        }
        else if (data.type === 'error') {
            setStatus('❌ خطا', 'error');
            addLog('❌ ' + data.message);
            document.getElementById('startBtn').disabled = false;
        }
    };

    ws.onerror = () => {
        setStatus('خطای اتصال', 'error');
        addLog('❌ خطا در اتصال به سرور');
    };

    ws.onclose = () => {
        setStatus('اتصال قطع شد', 'error');
        addLog('🔌 اتصال به سرور قطع شد');
    };
}

function startLogin() {
    const phone = document.getElementById('phone').value.trim();
    if (!phone) {
        setStatus('شماره تلفن را وارد کنید', 'error');
        return;
    }

    if (!ws || ws.readyState !== WebSocket.OPEN) {
        connect();
        setTimeout(() => startLogin(), 800);
        return;
    }

    document.getElementById('startBtn').disabled = true;
    document.getElementById('logBox').classList.remove('hidden');
    document.getElementById('logBox').innerHTML = '';
    addLog('🚀 شروع فرآیند ورود...');

    ws.send(JSON.stringify({ type: 'start_login', phone: phone }));
    setStatus('در حال ورود...', 'waiting');
}

function sendCode() {
    const code = document.getElementById('code').value.trim();
    if (!code) return;

    if (!ws || ws.readyState !== WebSocket.OPEN) return;

    ws.send(JSON.stringify({ type: 'code_input', code: code }));
    addLog('🔑 کد ارسال شد');
    document.getElementById('code').value = '';
    setStatus('در حال بررسی کد...', 'waiting');
}

// ارسال با Enter
document.getElementById('code').addEventListener('keypress', (e) => {
    if (e.key === 'Enter') sendCode();
});
document.getElementById('phone').addEventListener('keypress', (e) => {
    if (e.key === 'Enter') startLogin();
});

// اتصال اولیه
connect();
</script>
</body>
</html>
"""


# -------------------- اپلیکیشن FastAPI --------------------
app = FastAPI(title="Rubika Auto Login")

_ORIGINAL_INPUT = builtins.input


class LoginSession:
    def __init__(self, websocket, loop):
        self.ws = websocket
        self.loop = loop
        self.code_queue = queue.Queue()
        self.active = True

    def send_sync(self, data):
        if not self.active:
            return
        try:
            asyncio.run_coroutine_threadsafe(
                self.ws.send_json(data),
                self.loop
            )
        except Exception:
            pass

    def ask_input(self, prompt=""):
        self.send_sync({"type": "input_required", "prompt": str(prompt)})
        return self.code_queue.get()


def extract_user_info(me):
    info = {}
    if me is None:
        return info

    if isinstance(me, dict):
        for k, v in me.items():
            info[str(k)] = v if isinstance(v, (str, int, float, bool, type(None))) else str(v)
        return info

    if hasattr(me, "to_dict") and callable(getattr(me, "to_dict")):
        try:
            d = me.to_dict()
            for k, v in d.items():
                info[str(k)] = v if isinstance(v, (str, int, float, bool, type(None))) else str(v)
            return info
        except Exception:
            pass

    if hasattr(me, "__dict__"):
        for k, v in vars(me).items():
            if isinstance(v, (str, int, float, bool, type(None))):
                info[k] = v
            elif isinstance(v, (list, tuple)):
                info[k] = [str(x) for x in v]
            elif isinstance(v, dict):
                info[k] = {str(kk): str(vv) for kk, vv in v.items()}
            else:
                try:
                    info[k] = str(v)
                except Exception:
                    info[k] = "<?>"

    for attr in dir(me):
        if attr.startswith("_") or attr in info:
            continue
        try:
            v = getattr(me, attr)
            if callable(v):
                continue
            if isinstance(v, (str, int, float, bool, type(None))):
                info[attr] = v
            else:
                info[attr] = str(v)
        except Exception:
            continue

    return info


def run_login_thread(session, phone):
    def patched_input(prompt=""):
        return session.ask_input(prompt)

    builtins.input = patched_input

    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)

    try:
        loop.run_until_complete(_login_flow(session, phone))
    except Exception as e:
        session.send_sync({"type": "error", "message": f"خطا: {str(e)}"})
    finally:
        builtins.input = _ORIGINAL_INPUT
        try:
            loop.close()
        except Exception:
            pass
        session.active = False


async def _login_flow(session, phone):
    session.send_sync({"type": "log", "message": "🔌 در حال اتصال به سرور روبیکا..."})
    session.send_sync({"type": "log", "message": f"📱 شماره: {phone}"})

    try:
        bot = Client(name="web_rubika_session", phone_number=phone)
    except TypeError:
        bot = Client(name="web_rubika_session")

    async with bot:
        session.send_sync({"type": "log", "message": "✅ ورود با موفقیت انجام شد!"})

        session.send_sync({"type": "log", "message": "📋 در حال دریافت اطلاعات حساب..."})
        try:
            me = await bot.get_me()
            user_info = extract_user_info(me)
            session.send_sync({"type": "user_info", "data": user_info})
        except Exception as e:
            session.send_sync({"type": "log", "message": f"⚠️ خطا در دریافت اطلاعات: {e}"})

        try:
            await bot.send_message("me", "🎉 ورود با موفقیت انجام شد!")
            session.send_sync({"type": "log", "message": "📤 پیام تست ارسال شد"})
        except Exception as e:
            session.send_sync({"type": "log", "message": f"⚠️ خطا در ارسال پیام: {e}"})

        session.send_sync({"type": "success", "message": "ورود کامل شد"})


@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    loop = asyncio.get_event_loop()
    session = LoginSession(websocket, loop)

    try:
        await websocket.send_json({"type": "ready", "library": LIBRARY_NAME})

        while True:
            data = await websocket.receive_json()
            msg_type = data.get("type")

            if msg_type == "start_login":
                phone = (data.get("phone") or "").strip()
                if not phone:
                    await websocket.send_json({
                        "type": "error",
                        "message": "شماره تلفن خالی است"
                    })
                    continue

                t = threading.Thread(
                    target=run_login_thread,
                    args=(session, phone),
                    daemon=True
                )
                t.start()

            elif msg_type == "code_input":
                session.code_queue.put(data.get("code", ""))

    except WebSocketDisconnect:
        session.active = False
    except Exception:
        session.active = False


@app.get("/")
async def index():
    return HTMLResponse(HTML_PAGE)


if __name__ == "__main__":
    print("=" * 60)
    print("  🌐 سرور وب ورود به روبیکا")
    print(f"  📚 کتابخانه: {LIBRARY_NAME}")
    print("  🚀 آدرس: http://localhost:8000")
    print("=" * 60)
    uvicorn.run(app, host="0.0.0.0", port=8000)