#!/usr/bin/env python3
"""
Browser Cloud Service — Playwright-based browser automation server
Exposes a simple REST API for spawning/controlling browser sessions.
"""

import asyncio
import uuid
import json
import base64
from typing import Optional
from contextlib import asynccontextmanager

from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from playwright.async_api import async_playwright, TimeoutError as PWTimeoutError, BrowserContext, Page, Playwright

# Max concurrent browsers
MAX_BROWSERS = 5
BROWSER_TIMEOUT = 30000  # ms

# Active browser contexts
active_contexts: dict[str, BrowserContext] = {}
active_pages: dict[str, Page] = {}
active_playwrights: dict[str, Playwright] = {}


@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup
    print("[BrowserCloud] Iniciando Playwright service...")
    yield
    # Shutdown: close all browsers
    print(f"[BrowserCloud] Fechando {len(active_contexts)} contextos...")
    for ctx_id in list(active_contexts.keys()):
        try:
            await active_contexts[ctx_id].close()
        except Exception:
            pass
    active_contexts.clear()
    active_pages.clear()
    print("[BrowserCloud] Done.")


app = FastAPI(title="BrowserCloud", lifespan=lifespan)


# ─── Models ──────────────────────────────────────────────────────────────────

class BrowserOptions(BaseModel):
    headless: bool = True
    viewport_width: int = 1280
    viewport_height: int = 720
    user_agent: Optional[str] = None


class NavigateOptions(BaseModel):
    url: str
    wait_until: str = "load"  # load|domcontentloaded|networkidle


class ScreenshotOptions(BaseModel):
    full_page: bool = False
    format: str = "png"  # png|jpg


# ─── Helpers ───────────────────────────────────────────────────────────────────

def _get_page(ctx_id: str) -> Page:
    if ctx_id not in active_pages:
        raise HTTPException(404, f"Sessão {ctx_id} não encontrada")
    return active_pages[ctx_id]


async def _cleanup(ctx_id: str):
    """Close a session and remove from active dicts."""
    if ctx_id in active_pages:
        try:
            await active_pages[ctx_id].close()
        except Exception:
            pass
        del active_pages[ctx_id]
    if ctx_id in active_contexts:
        try:
            await active_contexts[ctx_id].close()
        except Exception:
            pass
        del active_contexts[ctx_id]
    if ctx_id in active_playwrights:
        try:
            await active_playwrights[ctx_id].stop()
        except Exception:
            pass
        del active_playwrights[ctx_id]


# ─── Endpoints ─────────────────────────────────────────────────────────────────

@app.get("/health")
async def health():
    return {
        "status": "ok",
        "active_sessions": len(active_contexts),
        "max_sessions": MAX_BROWSERS
    }


@app.post("/session")
async def create_session(opts: BrowserOptions = BrowserOptions()):
    """Cria uma nova sessão de browser (contexto + página)."""
    if len(active_contexts) >= MAX_BROWSERS:
        raise HTTPException(503, f"Limite de {MAX_BROWSERS} sessões atingido")

    ctx_id = uuid.uuid4().hex[:12]

    try:
        pw = await async_playwright().start()
        browser = await pw.chromium.launch(headless=opts.headless)
        context = await browser.new_context(
            viewport={"width": opts.viewport_width, "height": opts.viewport_height},
            user_agent=opts.user_agent or "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
            accept_downloads=True,
        )
        page = await context.new_page()
    except Exception as e:
        await pw.stop()
        raise HTTPException(500, f"Falha ao iniciar browser: {e}")

    active_contexts[ctx_id] = context
    active_pages[ctx_id] = page
    active_playwrights[ctx_id] = pw

    # Auto-cleanup on context close
    context.on("close", lambda: asyncio.create_task(_cleanup(ctx_id)))

    return {"session_id": ctx_id, "url": page.url}


@app.get("/session/{ctx_id}")
async def get_session(ctx_id: str):
    page = _get_page(ctx_id)
    return {
        "session_id": ctx_id,
        "url": page.url,
        "title": await page.title(),
    }


@app.delete("/session/{ctx_id}")
async def close_session(ctx_id: str):
    await _cleanup(ctx_id)
    return {"status": "closed", "session_id": ctx_id}


@app.post("/session/{ctx_id}/navigate")
async def navigate(ctx_id: str, opts: NavigateOptions):
    page = _get_page(ctx_id)
    try:
        await page.goto(opts.url, wait_until=opts.wait_until, timeout=BROWSER_TIMEOUT)
    except PWTimeoutError:
        raise HTTPException(408, "Navegação deu timeout")
    except Exception as e:
        raise HTTPException(500, str(e))

    return {"ok": True, "url": page.url, "title": await page.title()}


@app.post("/session/{ctx_id}/click")
async def click(ctx_id: str, selector: str, timeout: int = 10000):
    page = _get_page(ctx_id)
    try:
        await page.click(selector, timeout=timeout)
    except PWTimeoutError:
        raise HTTPException(408, f"Elemento não encontrado: {selector}")
    except Exception as e:
        raise HTTPException(500, str(e))
    return {"ok": True}


@app.post("/session/{ctx_id}/fill")
async def fill(ctx_id: str, selector: str, value: str, submit: bool = False):
    page = _get_page(ctx_id)
    try:
        await page.fill(selector, value, timeout=10000)
        if submit:
            await page.press(selector, "Enter")
    except Exception as e:
        raise HTTPException(500, str(e))
    return {"ok": True}


@app.get("/session/{ctx_id}/screenshot")
async def screenshot(ctx_id: str, opts: ScreenshotOptions = ScreenshotOptions()):
    page = _get_page(ctx_id)
    try:
        img = await page.screenshot(full_page=opts.full_page, type=opts.format)
        b64 = base64.b64encode(img).decode()
        return {"format": opts.format, "data": b64}
    except Exception as e:
        raise HTTPException(500, str(e))


@app.get("/session/{ctx_id}/html")
async def get_html(ctx_id: str):
    page = _get_page(ctx_id)
    return {"html": await page.content()}


@app.get("/session/{ctx_id}/text")
async def get_text(ctx_id: str, selector: str = "body"):
    page = _get_page(ctx_id)
    try:
        el = await page.query_selector(selector)
        if not el:
            raise HTTPException(404, f"Elemento {selector} não encontrado")
        text = await el.inner_text()
        return {"selector": selector, "text": text}
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(500, str(e))


@app.post("/session/{ctx_id}/evaluate")
async def evaluate(ctx_id: str, script: str):
    page = _get_page(ctx_id)
    try:
        result = await page.evaluate(script)
        return {"result": result}
    except Exception as e:
        raise HTTPException(500, str(e))


if __name__ == "__main__":
    import uvicorn
    port = int(__import__("os").getenv("PORT", 8088))
    uvicorn.run("browser_cloud:app", host="0.0.0.0", port=port, reload=False)
