# backend/src/agent/run.py
# #region AgentChat.Run [C:3] [TYPE Module] [SEMANTICS agent-chat,entrypoint,startup]
# @ingroup AgentChat
# @BRIEF Entrypoint for Gradio agent backend. Fetches LLM config from FastAPI on startup.
# @PRE FastAPI backend reachable at FASTAPI_URL. Service JWT available for auth.
# @POST Gradio agent running on configured port (auto-fallback to next free port if busy).
# @SIDE_EFFECT Binds to a TCP port via Gradio launch.
# @RATIONALE _find_free_port() prevents port conflicts when a previous agent instance is still running
#            without requiring manual cleanup or port-range environment variables.
# @REJECTED Failing hard on port-in-use was rejected — multiple restarts during development
#            should not require manual port cleanup.
import os
import socket
import httpx
import logging

logger = logging.getLogger("cot")

FASTAPI_URL = os.getenv("FASTAPI_URL", "http://localhost:8000")


def _find_free_port(start_port: int, max_attempts: int = 100) -> int:
    """Find a free TCP port starting from start_port, scanning up to max_attempts ports."""
    for port in range(start_port, start_port + max_attempts):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            try:
                s.bind(("", port))
                return port
            except OSError:
                continue
    raise OSError(f"No free port found in range {start_port}-{start_port + max_attempts - 1}")


def _fetch_llm_config() -> dict | None:
    """Fetch active LLM provider config from FastAPI with retry.

    Retries up to 30s (6 × 5s) to wait for FastAPI to be ready.
    Falls back to env vars if FastAPI is unreachable or returns no active provider.
    """
    import time
    service_token = os.getenv("SERVICE_JWT", "")
    headers = {"Authorization": f"Bearer {service_token}"} if service_token else {}

    for attempt in range(6):
        try:
            resp = httpx.get(f"{FASTAPI_URL}/api/agent/llm-config", headers=headers, timeout=5)
            resp.raise_for_status()
            config = resp.json()
            if config.get("configured"):
                logger.info("LLM config fetched from FastAPI: %s (%s)", config.get("provider_type"), config.get("default_model"))
                return config
            logger.warning("FastAPI returned no active LLM provider: %s", config.get("reason"))
        except Exception as e:
            if attempt < 5:
                logger.info("Waiting for FastAPI (attempt %d/6): %s", attempt + 1, e)
                time.sleep(5)
            else:
                logger.warning("Failed to fetch LLM config from FastAPI after 6 attempts: %s", e)
                logger.info("Falling back to env vars for LLM config")
    return None


if __name__ == "__main__":
    from src.agent.app import create_chat_interface
    from src.agent.context import set_service_jwt
    from src.agent.langgraph_setup import configure_from_api

    # Propagate SERVICE_JWT to ContextVar for tool calls
    service_token = os.getenv("SERVICE_JWT", "")
    if service_token:
        set_service_jwt(service_token)

    # Fetch LLM config from FastAPI at startup
    llm_config = _fetch_llm_config()
    if llm_config:
        configure_from_api(llm_config)

    # Find a free port — fallback if the configured port is already in use
    configured_port = int(os.getenv("GRADIO_SERVER_PORT", "7860"))
    try:
        port = _find_free_port(configured_port)
        if port != configured_port:
            logger.warning("Port %d is in use, falling back to port %d", configured_port, port)
    except OSError as e:
        logger.error("Failed to find a free port: %s", e)
        raise

    demo = create_chat_interface()
    demo.launch(
        server_name=os.getenv("GRADIO_SERVER_NAME", "0.0.0.0"),
        server_port=port,
    )
# #endregion AgentChat.Run