import atexit as _atexit
import json
import sys
from datetime import datetime
from pathlib import Path

from backend.config import OUTPUT_DIR


_BROWSER_PREVIEW_FILENAME = "browser_preview_live.png"
_BROWSER_PREVIEW_SCALE = 1.2
_browser_ctx: dict = {
    "browser": None,
    "context": None,
    "page": None,
    "preview_version": 0,
    "last_action": "",
    "last_error": "",
    "last_updated": "",
    "url": "",
    "title": "",
    "headless": True,
}


def _browser_cleanup() -> None:
    """Close Playwright browser and stop the playwright process. Safe to call multiple times."""
    try:
        ctx = _browser_ctx.get("context")
        if ctx:
            try:
                ctx.close()
            except Exception:
                pass
            _browser_ctx["context"] = None
        browser = _browser_ctx.get("browser")
        if browser:
            try:
                browser.close()
            except Exception:
                pass
            _browser_ctx["browser"] = None
        pw = _browser_ctx.get("_pw")
        if pw:
            try:
                pw.stop()
            except Exception:
                pass
            _browser_ctx["_pw"] = None
        _browser_ctx["page"] = None
    except Exception:
        pass


_atexit.register(_browser_cleanup)

_browser_on_update: callable = None


def _set_browser_on_update(callback) -> None:
    global _browser_on_update
    _browser_on_update = callback


def _browser_preview_path() -> Path:
    return OUTPUT_DIR / _BROWSER_PREVIEW_FILENAME


def _browser_effective_headless() -> bool:
    return True


def _browser_prepare_preview(page) -> None:
    try:
        page.evaluate(
            """
            (scale) => {
                const styleId = 'agent-browser-preview-scale';
                let styleEl = document.getElementById(styleId);
                if (!styleEl) {
                    styleEl = document.createElement('style');
                    styleEl.id = styleId;
                    document.head.appendChild(styleEl);
                }
                styleEl.textContent = `
                    html { zoom: ${scale}; }
                    body { overflow-x: hidden !important; }
                `;
            }
            """,
            _BROWSER_PREVIEW_SCALE,
        )
    except Exception:
        pass


def _browser_sync_state(*, capture_preview: bool = False, action: str = "", error: str = "") -> None:
    if action:
        _browser_ctx["last_action"] = str(action)
    if error:
        _browser_ctx["last_error"] = str(error)
    elif action:
        _browser_ctx["last_error"] = ""

    page = _browser_ctx.get("page")
    if page is not None:
        try:
            _browser_ctx["url"] = str(page.url or "")
        except Exception:
            pass
        try:
            _browser_ctx["title"] = str(page.title() or "")
        except Exception:
            pass

        if capture_preview:
            try:
                OUTPUT_DIR.mkdir(exist_ok=True)
                _browser_prepare_preview(page)
                page.screenshot(path=str(_browser_preview_path()), full_page=False)
                _browser_ctx["preview_version"] = int(datetime.now().timestamp() * 1000)
            except Exception as exc:
                _browser_ctx["last_error"] = f"Preview capture error: {exc}"

    _browser_ctx["last_updated"] = datetime.now().isoformat()

    if _browser_on_update and (capture_preview or action or error):
        try:
            _browser_on_update(_browser_preview_state())
        except Exception:
            pass


def _browser_preview_state() -> dict:
    page = _browser_ctx.get("page")
    headless = _browser_effective_headless()
    if _browser_ctx.get("browser") is not None:
        headless = bool(_browser_ctx.get("headless", headless))

    preview_path = _browser_preview_path()
    preview_available = preview_path.exists() and preview_path.is_file()
    active = page is not None
    status = str(_browser_ctx.get("last_error") or "").strip()
    if not status:
        if active:
            status = str(_browser_ctx.get("last_action") or "Browser session ready").strip()
        else:
            status = "No browser session active yet."

    return {
        "active": active,
        "headless": headless,
        "status": status,
        "url": str(_browser_ctx.get("url") or "").strip(),
        "title": str(_browser_ctx.get("title") or "").strip(),
        "last_action": str(_browser_ctx.get("last_action") or "").strip(),
        "last_error": str(_browser_ctx.get("last_error") or "").strip(),
        "last_updated": str(_browser_ctx.get("last_updated") or "").strip(),
        "preview_available": preview_available,
        "preview_version": int(_browser_ctx.get("preview_version") or 0),
    }


def _browser_install_chromium_once() -> str | None:
    if _browser_ctx.get("_chromium_install_attempted"):
        return "Chromium browser for Playwright is missing and auto-install already failed in this session."
    _browser_ctx["_chromium_install_attempted"] = True
    try:
        import subprocess

        subprocess.run(
            [sys.executable, "-m", "playwright", "install", "chromium"],
            check=True,
            capture_output=True,
            text=True,
            timeout=240,
        )
        return None
    except Exception as exc:
        return f"Chromium browser install failed: {exc}"


def _browser_import_sync_playwright():
    try:
        from playwright.sync_api import sync_playwright

        return sync_playwright, None
    except Exception:
        if _browser_ctx.get("_playwright_bootstrap_attempted"):
            return None, "Playwright is not available and auto-install already failed in this session."
        _browser_ctx["_playwright_bootstrap_attempted"] = True
        try:
            import subprocess

            subprocess.run(
                [sys.executable, "-m", "pip", "install", "playwright"],
                check=True,
                capture_output=True,
                text=True,
                timeout=240,
            )
            subprocess.run(
                [sys.executable, "-m", "playwright", "install", "chromium"],
                check=True,
                capture_output=True,
                text=True,
                timeout=240,
            )
        except Exception as exc:
            return None, f"Playwright auto-install failed: {exc}"

        try:
            from playwright.sync_api import sync_playwright

            return sync_playwright, None
        except Exception as exc:
            return None, f"Playwright import still failed after auto-install: {exc}"


def _browser_ensure_page():
    """Lazy-init a Playwright Chromium page. Reuses the same page across calls."""
    playwright_instance = _browser_ctx.get("_pw")
    if playwright_instance is None:
        sync_playwright, err = _browser_import_sync_playwright()
        if err:
            return None, err
        playwright_instance = sync_playwright().start()
        _browser_ctx["_pw"] = playwright_instance
    if _browser_ctx["browser"] is None:
        headless = _browser_effective_headless()
        try:
            _browser_ctx["browser"] = playwright_instance.chromium.launch(headless=bool(headless))
        except Exception as exc:
            msg = str(exc)
            if "Executable doesn't exist" in msg or "playwright install" in msg.lower():
                install_err = _browser_install_chromium_once()
                if install_err:
                    _browser_sync_state(error=install_err)
                    return None, install_err
                _browser_ctx["browser"] = playwright_instance.chromium.launch(headless=bool(headless))
            else:
                _browser_sync_state(error=f"Browser launch error: {exc}")
                return None, f"Browser launch error: {exc}"
        _browser_ctx["headless"] = bool(headless)
        _browser_ctx["context"] = _browser_ctx["browser"].new_context(
            viewport={"width": 1440, "height": 900},
            user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
        )
        _browser_ctx["page"] = _browser_ctx["context"].new_page()
        _browser_prepare_preview(_browser_ctx["page"])
        _browser_sync_state(capture_preview=True, action="Browser session ready")
    return _browser_ctx["page"], None


def _browser_highlight_element(page, selector: str) -> None:
    """Inject a brief visual highlight on the target element for the live preview."""
    try:
        page.evaluate(
            """
            (sel) => {
                const el = document.querySelector(sel);
                if (!el) return;
                const overlay = document.createElement('div');
                overlay.id = '__agent_highlight__';
                const rect = el.getBoundingClientRect();
                Object.assign(overlay.style, {
                    position: 'fixed',
                    left: rect.left - 4 + 'px',
                    top:  rect.top  - 4 + 'px',
                    width:  rect.width  + 8 + 'px',
                    height: rect.height + 8 + 'px',
                    border: '3px solid #ef4444',
                    borderRadius: '6px',
                    background: 'rgba(239,68,68,0.10)',
                    zIndex: '2147483647',
                    pointerEvents: 'none',
                    boxShadow: '0 0 12px rgba(239,68,68,0.35)',
                    transition: 'opacity 0.3s ease',
                });
                document.body.appendChild(overlay);
            }
            """,
            selector,
        )
    except Exception:
        pass


def _browser_remove_highlight(page) -> None:
    """Remove the highlight overlay after the action screenshot is taken."""
    try:
        page.evaluate("document.getElementById('__agent_highlight__')?.remove()")
    except Exception:
        pass


def _browser_navigate(url: str, wait_until: str = "domcontentloaded") -> str:
    page, err = _browser_ensure_page()
    if err:
        return err
    try:
        page.goto(url, wait_until=wait_until, timeout=30000)
        title = page.title()
        result = f"Navigated to {page.url} — title: {title}"
        _browser_sync_state(capture_preview=True, action=result)
        return result
    except Exception as e:
        _browser_sync_state(error=f"Browser navigate error: {e}")
        return f"Browser navigate error: {e}"


def _browser_click(selector: str) -> str:
    page, err = _browser_ensure_page()
    if err:
        return err
    try:
        _browser_highlight_element(page, selector)
        _browser_sync_state(capture_preview=True, action=f"About to click '{selector}'")
        _browser_remove_highlight(page)
        page.click(selector, timeout=10000)
        page.wait_for_load_state("domcontentloaded", timeout=10000)
        result = f"Clicked '{selector}' — now on {page.url}"
        _browser_sync_state(capture_preview=True, action=result)
        return result
    except Exception as e:
        _browser_remove_highlight(page)
        _browser_sync_state(error=f"Browser click error: {e}")
        return f"Browser click error: {e}"


def _browser_type(selector: str, text: str, press_enter: bool = False) -> str:
    page, err = _browser_ensure_page()
    if err:
        return err
    try:
        _browser_highlight_element(page, selector)
        _browser_sync_state(capture_preview=True, action=f"About to type into '{selector}'")
        _browser_remove_highlight(page)
        page.fill(selector, text, timeout=10000)
        if press_enter:
            page.press(selector, "Enter")
            page.wait_for_load_state("domcontentloaded", timeout=10000)
        result = f"Typed into '{selector}'" + (" and pressed Enter" if press_enter else "")
        _browser_sync_state(capture_preview=True, action=result)
        return result
    except Exception as e:
        _browser_remove_highlight(page)
        _browser_sync_state(error=f"Browser type error: {e}")
        return f"Browser type error: {e}"


def _browser_screenshot() -> str:
    page, err = _browser_ensure_page()
    if err:
        return err
    try:
        OUTPUT_DIR.mkdir(exist_ok=True)
        fname = f"screenshot_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
        dest = OUTPUT_DIR / fname
        page.screenshot(path=str(dest), full_page=False)
        result = f"Screenshot saved: output/{fname} ({dest.stat().st_size:,} bytes). Current page: {page.url}"
        _browser_sync_state(capture_preview=True, action=result)
        return result
    except Exception as e:
        _browser_sync_state(error=f"Browser screenshot error: {e}")
        return f"Browser screenshot error: {e}"


def _browser_get_text(selector: str = "body", max_chars: int = 12000) -> str:
    page, err = _browser_ensure_page()
    if err:
        return err
    try:
        el = page.query_selector(selector)
        if el is None:
            return f"No element found for selector '{selector}'"
        text = el.inner_text()
        url = page.url
        if len(text) > max_chars:
            text = text[:max_chars] + f"\n…(truncated, {len(text)} total chars)"
        _browser_sync_state(action=f"Extracted text from '{selector}'")
        return f"[{url}]\n{text}"
    except Exception as e:
        _browser_sync_state(error=f"Browser get_text error: {e}")
        return f"Browser get_text error: {e}"


def _browser_eval(js_code: str) -> str:
    page, err = _browser_ensure_page()
    if err:
        return err
    try:
        result = page.evaluate(js_code)
        _browser_sync_state(action="Executed browser script")
        return json.dumps(result, default=str, ensure_ascii=False)[:8000]
    except Exception as e:
        _browser_sync_state(error=f"Browser eval error: {e}")
        return f"Browser eval error: {e}"