import asyncio, json, logging, os, secrets
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request, Form, HTTPException, Cookie
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from playwright.async_api import async_playwright
from telegram import Bot
import aiosqlite

from database import (init_db, create_user, get_user_by_key, set_telegram,
    mark_key_sent, get_session, save_session, set_session_status,
    update_settings, save_push_subscription, get_all_active_sessions)
from manager import WorkerManager

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

TG_BOT_TOKEN   = os.environ.get("TG_BOT_TOKEN", "")
ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD", "admin123")
with open("/var/www/html/maxservice/private.pem", "r") as f:
    VAPID_PRIVATE = f.read()
VAPID_PUBLIC   = os.environ.get("VAPID_PUBLIC", "")
VAPID_EMAIL    = os.environ.get("VAPID_EMAIL", "admin@example.com")
TG_BOT_LINK    = "https://t.me/Maxmes312bot"
USERINFOBOT    = "https://t.me/userinfobot"
MAX_URL        = "https://web.max.ru"

POLL_OPTIONS = [(300,"5 минут"),(900,"15 минут"),(1800,"30 минут"),(3600,"1 час"),(21600,"6 часов"),(60,"Тестирование")]

bot: Bot = None
manager: WorkerManager = None
qr_sessions: dict = {}
ADMIN_SESSIONS: set = set()


@asynccontextmanager
async def lifespan(app: FastAPI):
    global bot, manager
    await init_db()
    bot = Bot(token=TG_BOT_TOKEN)
    manager = WorkerManager(bot, VAPID_PRIVATE, VAPID_EMAIL)
    await manager.start_all()
    log.info("Сервис запущен")
    yield

# Передаем root_path, чтобы FastAPI автоматически добавлял /maxmes ко всем ссылкам и статике
app = FastAPI(lifespan=lifespan, root_path="/maxmes")
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")

# ─── Страницы ─────────────────────────────────────────────────────────────────
def get_private_key():
    with open("/var/www/html/maxservice/private.pem", "r") as f:
        return f.read()
@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
    return templates.TemplateResponse(request=request, name="index.html", context={})

@app.get("/setup", response_class=HTMLResponse)
async def setup(request: Request):
    return templates.TemplateResponse(request=request, name="setup.html", context={})

@app.get("/dashboard/{key}", response_class=HTMLResponse)
async def dashboard(request: Request, key: str):
    user = await get_user_by_key(key)
    if not user:
        return RedirectResponse("/")
    
    session = await get_session(user["id"])
    
    # ИСПРАВЛЕНИЕ: Берем статус напрямую из базы данных
    status = session["status"] if session else "no_session"
    
    # Подстраховка: если сессия в БД активна, но воркер в фоне упал или не запустился
    if session and session["status"] == "active" and session.get("session_json"):
        if manager.get_status(user["id"]) != "active":
            log.info(f"Принудительный запуск упавшего воркера для пользователя {user['id']}")
            await manager.start_worker(
                user_id=user["id"],
                telegram_chat_id=user.get("telegram_chat_id"),
                session_json=session["session_json"],
                poll_interval=session.get("poll_interval", 300),
                mute_filter=bool(session.get("mute_filter", 1)),
                notify_telegram=bool(session.get("notify_telegram", 0)),
                notify_webpush=bool(session.get("notify_webpush", 0)),
                push_subscription=session.get("push_subscription"),
            )
            
    return templates.TemplateResponse(request=request, name="dashboard.html", context={
        "key": key,
        "user": user,
        "status": status,
        "session": session,
        "poll_options": POLL_OPTIONS,
        "vapid_public": VAPID_PUBLIC,
        "tg_bot_link": TG_BOT_LINK,
        "userinfobot": USERINFOBOT,
    })

@app.get("/webpush-help", response_class=HTMLResponse)
async def webpush_help(request: Request):
    return templates.TemplateResponse(request=request, name="webpush_help.html", context={})

@app.get("/static/manifest.json")
async def manifest():
    return JSONResponse({
        "name": "MAX Bridge",
        "short_name": "MAX Bridge",
        "start_url": "./webpush-help",
        "scope": "./",
        "display": "standalone",
        "background_color": "#f0f2f5",
        "theme_color": "#4f8ef7",
        "icons": [
            {"src": "./static/icon.png", "sizes": "192x192", "type": "image/png"},
            {"src": "./static/icon.png", "sizes": "512x512", "type": "image/png"}
        ]
    })

# ─── API ──────────────────────────────────────────────────────────────────────

@app.get("/api/check-key")
async def check_key(key: str):
    user = await get_user_by_key(key)
    return JSONResponse({"ok": bool(user)})
@app.post("/api/send-test-push")
async def send_test_push(key: str = Form(...)):
    user = await get_user_by_key(key)
    if not user:
        raise HTTPException(status_code=403, detail="Ключ не найден")

    session = await get_session(user["id"])

    result = {
        "telegram": False,
        "webpush": False
    }

    # Telegram
    if user.get("telegram_chat_id"):
        try:
            await bot.send_message(
                chat_id=user["telegram_chat_id"],
                text="🧪 Тестовое уведомление MAX Bridge"
            )
            result["telegram"] = True
        except Exception as e:
            result["telegram_error"] = str(e)
            log.error(f"Test TG error: {e}")

    # WebPush
    subscription_info = session.get("push_subscription") if session else None

    if subscription_info:
        try:
            from pywebpush import webpush

            sub_info = (
                json.loads(subscription_info)
                if isinstance(subscription_info, str)
                else subscription_info
            )

            webpush(
                subscription_info=sub_info,
                data=json.dumps({
                    "title": "Тест MAX Bridge",
                    "body": "Это тестовое уведомление! ✅"
                }),
                vapid_private_key="/var/www/html/maxservice/private.pem",
                vapid_claims={
                    "sub": f"mailto:{VAPID_EMAIL}"
                }
            )

            result["webpush"] = True

        except Exception as e:
            result["webpush_error"] = str(e)
            log.error(f"Test push error: {e}")

    return result
@app.post("/api/start-qr")
async def start_qr():
    # Создаём нового пользователя с 5-значным ключом
    user = await create_user()
    key = user["access_key"]

    if key in qr_sessions:
        try:
            await qr_sessions[key]["context"].close()
            await qr_sessions[key]["browser"].close()
            await qr_sessions[key]["pw"].stop()
        except: pass

    pw = await async_playwright().start()
    browser = await pw.chromium.launch(headless=True, args=[
        "--no-sandbox",
        "--disable-setuid-sandbox",
        "--disable-dev-shm-usage",  # Критично для Docker / слабых VPS (использует /tmp вместо /dev/shm)
        "--disable-accelerated-2d-canvas",
        "--disable-gpu",            # Отключает использование видеокарты
        "--no-first-run",
        "--no-zygote",
        "--single-process",         # Запускает браузер в ОДНОМ процессе вместо размножения в фоне (сильно экономит RAM)
    ])
    context = await browser.new_context(viewport={"width": 1280, "height": 800})
    page = await context.new_page()
    await page.goto(MAX_URL, wait_until="networkidle")
    await page.wait_for_timeout(2000)

    try:
        qr_el = page.locator("canvas").first
        await qr_el.wait_for(timeout=5000)
        qr_bytes = await qr_el.screenshot()
    except:
        qr_bytes = await page.screenshot()

    qr_sessions[key] = {"pw": pw, "browser": browser, "context": context, "page": page, "user_id": user["id"]}
    asyncio.create_task(wait_for_login(key, user["id"]))

    import base64
    return JSONResponse({"qr": base64.b64encode(qr_bytes).decode(), "key": key})


async def wait_for_login(key: str, user_id: int):
    sess = qr_sessions.get(key)
    if not sess:
        return
    page, context = sess["page"], sess["context"]
    try:
        for _ in range(36):
            await asyncio.sleep(5)
            logged = await page.evaluate("""
                () => { const a = localStorage.getItem('__oneme_auth');
                    if (!a) return false;
                    try { return !!JSON.parse(a).token; } catch(e) { return false; } }
            """)
            if logged:
                break
        else:
            return
        storage_json = json.dumps(await context.storage_state())
        
        # 1. Сохраняем сессию со статусом active в БД
        await save_session(user_id, storage_json)
        
        # 2. ИСПРАВЛЕНИЕ: Сразу же запускаем воркер в фоне, чтобы статус изменился везде
        user = await get_user_by_key(key)
        await manager.start_worker(
            user_id=user_id,
            telegram_chat_id=user.get("telegram_chat_id") if user else None,
            session_json=storage_json,
            poll_interval=300,
            mute_filter=True,
            notify_telegram=False,
            notify_webpush=False
        )
        
        log.info(f"MAX привязан и воркер запущен, ключ={key}")
    except Exception as e:
        log.error(f"Ошибка QR ключ={key}: {e}")
    finally:
        try:
            await context.close()
            await sess["browser"].close()
            await sess["pw"].stop()
        except: pass
        qr_sessions.pop(key, None)

@app.get("/api/qr-status/{key}")
async def qr_status(key: str):
    user = await get_user_by_key(key)
    if not user:
        return JSONResponse({"status": "waiting"})
        
    session = await get_session(user["id"])
    
    if session:
        # Если сессия уже active — всё отлично, пропускаем пользователя
        if session["status"] == "active":
            return JSONResponse({"status": "connected"})
            
        # АНТИ-БАГ: Если Playwright уже перехватил сессию (session_json не пустой), 
        # но статус в базе всё ещё висит в 'pending' из-за задержки асинхронного коммита
        if session.get("session_json") and session["status"] == "pending":
            log.info(f"Устранение race condition для ключа {key}: принудительная активация.")
            
            # 1. Жестко переводим статус в active в базе данных
            await set_session_status(user["id"], "active")
            
            # 2. Сразу же запускаем фоновый воркер, чтобы уведомления начали работать
            await manager.start_worker(
                user_id=user["id"],
                telegram_chat_id=user.get("telegram_chat_id"),
                session_json=session["session_json"],
                poll_interval=session.get("poll_interval", 300),
                mute_filter=bool(session.get("mute_filter", 1)),
                notify_telegram=bool(session.get("notify_telegram", 0)),
                notify_webpush=bool(session.get("notify_webpush", 0)),
                push_subscription=session.get("push_subscription")
            )
            
            return JSONResponse({"status": "connected"})
            
    return JSONResponse({"status": "waiting"})

@app.post("/api/set-telegram")
async def api_set_telegram(key: str = Form(...), tg_id: str = Form(...)):
    user = await get_user_by_key(key)
    if not user:
        raise HTTPException(404)
    await set_telegram(user["id"], tg_id)
    # Обновляем воркер
    session = await get_session(user["id"])
    if session and session.get("session_json"):
        await manager.start_worker(
            user_id=user["id"], telegram_chat_id=tg_id,
            session_json=session["session_json"],
            poll_interval=session.get("poll_interval", 300),
            mute_filter=bool(session.get("mute_filter", 1)),
            notify_telegram=bool(session.get("notify_telegram", 0)),
            notify_webpush=bool(session.get("notify_webpush", 0)),
            push_subscription=session.get("push_subscription"),
        )
    # Отправляем ключ если ещё не отправляли
    if not user.get("key_sent"):
        try:
            await bot.send_message(tg_id,
                f"✅ MAX Bridge подключён!\n\nВаш ключ доступа: <b>{key}</b>\n\nСохраните его — он нужен для входа на сайт.",
                parse_mode="HTML")
            await mark_key_sent(user["id"])
        except Exception as e:
            log.error(f"Не удалось отправить ключ в TG: {e}")
    return JSONResponse({"ok": True})


@app.post("/api/save-push")
async def save_push(key: str = Form(...), subscription: str = Form(...)):
    user = await get_user_by_key(key)
    if not user:
        raise HTTPException(404)
    await save_push_subscription(user["id"], subscription)
    # Обновляем notify_webpush=1
    session = await get_session(user["id"])
    if session:
        await update_settings(user["id"], session.get("poll_interval", 300),
            session.get("mute_filter", 1), session.get("notify_telegram", 0), 1)
    # Отправляем ключ если ещё не отправляли (через push не можем, шлём в консоль)
    if not user.get("key_sent"):
        await mark_key_sent(user["id"])
    return JSONResponse({"ok": True})


@app.get("/api/vapid-public")
async def vapid_public():
    return JSONResponse({"key": VAPID_PUBLIC})


@app.post("/api/settings")
async def api_settings(
    key: str = Form(...),
    poll_interval: int = Form(300),
    mute_filter: int = Form(0),
    notify_telegram: int = Form(0),
    notify_webpush: int = Form(0),
):
    user = await get_user_by_key(key)
    if not user:
        raise HTTPException(404)
    await update_settings(user["id"], poll_interval, mute_filter, notify_telegram, notify_webpush)
    session = await get_session(user["id"])
    if session and session.get("session_json"):
        await manager.start_worker(
            user_id=user["id"], telegram_chat_id=user.get("telegram_chat_id"),
            session_json=session["session_json"],
            poll_interval=poll_interval, mute_filter=bool(mute_filter),
            notify_telegram=bool(notify_telegram), notify_webpush=bool(notify_webpush),
            push_subscription=session.get("push_subscription"),
        )
    return JSONResponse({"ok": True})


@app.post("/api/disconnect")
async def api_disconnect(key: str = Form(...)):
    user = await get_user_by_key(key)
    if not user:
        raise HTTPException(404)
    manager.stop_worker(user["id"])
    await set_session_status(user["id"], "expired")
    return JSONResponse({"ok": True})


# ─── Админка ──────────────────────────────────────────────────────────────────

def check_admin(token=None):
    return bool(token and token in ADMIN_SESSIONS)

@app.get("/admin", response_class=HTMLResponse)
async def admin_page(request: Request, admin_token: str = Cookie(default=None)):
    if not check_admin(admin_token):
        return RedirectResponse("/admin/login")
    async with aiosqlite.connect("maxservice.db") as db:
        db.row_factory = aiosqlite.Row
        async with db.execute("""SELECT u.id as user_id, u.access_key, u.telegram_chat_id,
            u.created_at, s.status, s.poll_interval, s.notify_telegram, s.notify_webpush
            FROM users u LEFT JOIN sessions s ON s.user_id=u.id
            ORDER BY u.created_at DESC""") as cur:
            users = [dict(r) for r in await cur.fetchall()]
    for u in users:
        if manager.get_status(u["user_id"]) == "active":
            u["status"] = "active"
    return templates.TemplateResponse(request=request, name="admin.html", context={"users": users})

@app.get("/admin/login", response_class=HTMLResponse)
async def admin_login_page(request: Request):
    return templates.TemplateResponse(request=request, name="admin_login.html", context={"error": None})

@app.post("/admin/login")
async def admin_login(request: Request, password: str = Form(...)):
    if password == ADMIN_PASSWORD:
        token = secrets.token_hex(32)
        ADMIN_SESSIONS.add(token)
        resp = RedirectResponse("/admin", status_code=303)
        resp.set_cookie("admin_token", token, httponly=True, max_age=86400*7)
        return resp
    return templates.TemplateResponse(request=request, name="admin_login.html", context={"error": "Неверный пароль"})

@app.get("/admin/logout")
async def admin_logout(admin_token: str = Cookie(default=None)):
    ADMIN_SESSIONS.discard(admin_token)
    resp = RedirectResponse("/admin/login")
    resp.delete_cookie("admin_token")
    return resp

@app.post("/admin/disconnect")
async def admin_disconnect(request: Request, user_id: int = Form(...), admin_token: str = Cookie(default=None)):
    if not check_admin(admin_token):
        raise HTTPException(403)
    manager.stop_worker(user_id)
    await set_session_status(user_id, "expired")
    async with aiosqlite.connect("maxservice.db") as db:
        async with db.execute("SELECT telegram_chat_id FROM users WHERE id=?", (user_id,)) as cur:
            row = await cur.fetchone()
            if row and row[0]:
                try: await bot.send_message(row[0], "⚠️ Ваш аккаунт MAX отключён администратором.")
                except: pass
    return RedirectResponse("/admin", status_code=303)
