"""
agentradar — Local-first visual debugger for multi-agent AI systems.

Usage:
    import agentradar          # auto-detects frameworks, opens dashboard

    from agentradar import radar

    @radar
    def my_agent(state): ...

    @radar(tag="v2", notes="new prompt")
    async def my_agent(state): ...
"""
from __future__ import annotations

import atexit
import logging
import os
import socket
import sys
import threading
import time
import webbrowser
from typing import Optional

__version__ = "1.0.0"
__all__ = ["radar", "config", "snapshot", "reset_run"]

logger = logging.getLogger("agentradar")

# Public decorator import
from agentradar.instrumentation.decorator import radar, reset_run  # noqa: E402

# Server handle
_server_thread: Optional[threading.Thread] = None
_server_port: Optional[int] = None
_server_started = threading.Event()
_config: dict = {}


def config(
    port: int = 7111,
    open_browser: bool = True,
    token_budget: Optional[float] = None,
    timeout: int = 30,
    theme: str = "dark",
    patch: bool = True,
) -> None:
    """Configure agentradar. Call before running your pipeline."""
    global _config
    _config.update({
        "port": port,
        "open_browser": open_browser,
        "token_budget": token_budget,
        "timeout": timeout,
        "theme": theme,
        "patch": patch,
    })


def snapshot(name: str = "snapshot", output_dir: Optional[str] = None) -> str:
    """Save the current run as a portable .agtrace snapshot file."""
    from agentradar.server.app import _run_state
    from agentradar.storage.snapshots import save_snapshot
    from pathlib import Path

    path = save_snapshot(
        _run_state.copy(),
        name=name,
        output_dir=Path(output_dir) if output_dir else None,
    )
    print(f"Snapshot saved: {path}")
    return str(path)


def _find_free_port(start: int, end: int) -> int:
    for port in range(start, end + 1):
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                s.bind(("", port))
                return port
        except OSError:
            continue
    raise RuntimeError(f"No free port found in range {start}-{end}")


def _start_server(port: int) -> None:
    global _server_port
    import uvicorn
    from agentradar.server.app import app

    _server_port = port
    _server_started.set()
    uvicorn.run(
        app,
        host="0.0.0.0",
        port=port,
        log_level="warning",
        access_log=False,
    )


def _bootstrap() -> None:
    """Run on import: detect frameworks, start server, open browser."""
    global _server_thread, _config

    # Load persisted config as defaults
    try:
        from agentradar.storage.config import load_config
        saved = load_config()
        merged = {**saved, **_config}
        _config = merged
    except Exception:
        pass

    port_start = _config.get("port", 7111)
    open_browser = _config.get("open_browser", True)
    do_patch = _config.get("patch", True)

    # Find free port
    try:
        port = _find_free_port(port_start, port_start + 9)
    except RuntimeError:
        port = port_start

    if port != port_start:
        print(f"agentradar: port {port_start} in use, using {port}")

    # Start server in background thread
    _server_thread = threading.Thread(
        target=_start_server,
        args=(port,),
        daemon=True,
        name="agentradar-server",
    )
    _server_thread.start()

    # Wait for server to be ready (max 3s)
    _server_started.wait(timeout=3.0)
    time.sleep(0.8)  # Give uvicorn a moment to bind

    # Patch LangChain to capture token usage from AIMessage responses
    try:
        from agentradar.server.events import patch_langchain
        patch_langchain()
    except Exception as e:
        logger.debug("LangChain token patch failed: %s", e)

    # Auto-detect and patch frameworks
    detected = []
    if do_patch:
        try:
            from agentradar.instrumentation.auto_detect import detect_and_patch
            detected = detect_and_patch()
        except Exception as e:
            logger.debug("Auto-detect failed: %s", e)

    # Always activate the sys.setprofile auto-tracer for __main__ functions.
    # Safe alongside framework patchers: the profiler guards against double-tracing
    # by checking if the caller is a @radar sync_wrapper/async_wrapper.
    try:
        from agentradar.instrumentation.profiler import start as _profiler_start
        _profiler_start()
    except Exception as e:
        logger.debug("Profiler start failed: %s", e)

    # Emit framework info
    if detected:
        try:
            from agentradar.server.events import emit, build_event, make_run_id
            from agentradar.instrumentation.decorator import _current_run_id
            run_id = _current_run_id or make_run_id()
            ev = build_event(
                event_type="frameworks_detected",
                run_id=run_id,
                agent_id="__system__",
                agent_name="system",
            )
            ev["frameworks"] = detected
            emit(ev)
        except Exception:
            pass

    # Open browser
    if open_browser:
        def _open_browser():
            time.sleep(0.5)
            webbrowser.open(f"http://localhost:{port}")
        threading.Thread(target=_open_browser, daemon=True).start()

    # Register cleanup
    atexit.register(_shutdown)


def _shutdown() -> None:
    """Called on Python process exit."""
    try:
        from agentradar.instrumentation.decorator import _current_run_id
        from agentradar.server.events import emit, build_event
        if _current_run_id:
            emit(build_event(
                event_type="run_end",
                run_id=_current_run_id,
                agent_id="__run__",
                agent_name="run",
                agent_type="agent",
            ))
            time.sleep(0.3)  # Let events drain
    except Exception:
        pass


# Run bootstrap on import (skip if running tests with AGENT_TRACE_NO_SERVER=1)
if os.environ.get("AGENT_TRACE_NO_SERVER") != "1":
    _bootstrap()
