"""AlbusHTTPServer - Main HTTP server entry point.

Enterprise-grade lifecycle:
    - Configurable timeouts, body limits, concurrency caps
    - SIGTERM / SIGINT → graceful shutdown with drain period
    - Concurrency semaphore for expensive LLM-backed operations

Usage:
    # Programmatic
    server = AlbusHTTPServer.create(pathway_vm=vm)
    await server.start(host="0.0.0.0", port=8080)

    # CLI
    python -m server.transport.server
"""

from __future__ import annotations

import argparse
import asyncio
import logging
import os
import signal
import sys
from pathlib import Path

from aiohttp import web
from dotenv import load_dotenv

from core.observability import configure_logging
from core.pathways.context import Context
from core.pathways.vm import PathwayVM
from core.tools import get_tool_registry
from server.config import DeploymentConfig
from server.config.env import get_config
from server.loaders import apply_model_config, load_agents_from_directories
from server.runtime import Runtime
from server.transport.http import create_http_app

logger = logging.getLogger(__name__)


class AlbusHTTPServer:
    """Albus HTTP server with enterprise lifecycle management.

    Features:
    - Graceful shutdown on SIGTERM/SIGINT with configurable drain period
    - Concurrency semaphore for LLM-backed operations
    - Max body size enforcement via aiohttp
    """

    def __init__(self, runtime: Runtime, app: web.Application):
        self._runtime = runtime
        self._app = app
        self._runner: web.AppRunner | None = None
        self._shutdown_event: asyncio.Event = asyncio.Event()
        self._turn_semaphore: asyncio.Semaphore | None = None

    @classmethod
    async def create(
        cls,
        *,
        pathway_vm: PathwayVM,
        debug: bool = False,
    ) -> AlbusHTTPServer:
        """Create server with runtime."""
        config = get_config()
        runtime = await Runtime.create(
            pathway_vm=pathway_vm,
            debug=debug,
            persist_dir=config.persistence.persist_dir,
        )
        app = create_http_app(runtime)
        server = cls(runtime=runtime, app=app)
        server._turn_semaphore = asyncio.Semaphore(config.server.max_concurrent_turns)
        # Expose semaphore on app state so handlers can acquire it
        app["turn_semaphore"] = server._turn_semaphore
        return server

    @property
    def runtime(self) -> Runtime:
        return self._runtime

    async def start(self, host: str = "127.0.0.1", port: int = 8080) -> None:
        """Start the server. Runs until SIGTERM/SIGINT or shutdown_event is set."""
        config = get_config()

        self._runner = web.AppRunner(
            self._app,
            client_max_size=config.server.max_body_size,
        )
        await self._runner.setup()

        site = web.TCPSite(self._runner, host, port)
        await site.start()

        # Register signal handlers for graceful shutdown
        loop = asyncio.get_running_loop()
        for sig in (signal.SIGTERM, signal.SIGINT):
            loop.add_signal_handler(sig, self._handle_signal, sig)

        logger.info("Albus server started at http://%s:%d", host, port)
        print(f"\n🧙 Albus server running at http://{host}:{port}")
        print("  POST /api/v1/agents/<id>/turn  - Agent turn")
        print("  POST /api/v1/pathways/<id>/run  - Run pathway")
        print("  GET  /api/v1/agents             - List agents")
        print("  GET  /api/v1/pathways           - List pathways")
        print("  GET  /api/v1/healthz            - Liveness probe")
        print("  GET  /api/v1/readyz             - Readiness probe")
        print("\nPress Ctrl+C to stop\n")

        # Block until shutdown signal
        await self._shutdown_event.wait()

    def _handle_signal(self, sig: signal.Signals) -> None:
        """Signal handler — triggers graceful shutdown."""
        sig_name = signal.Signals(sig).name
        logger.info("Received %s — initiating graceful shutdown", sig_name)
        print(f"\n⏳ Received {sig_name}, shutting down gracefully...")
        self._shutdown_event.set()

    async def stop(self) -> None:
        """Gracefully stop the server.

        1. Stop accepting new connections
        2. Wait up to shutdown_timeout for in-flight requests to drain
        3. Force-close remaining connections
        """
        if not self._runner:
            return

        config = get_config()
        timeout = config.server.shutdown_timeout

        logger.info("Draining in-flight requests (timeout=%ds)...", timeout)

        try:
            await asyncio.wait_for(
                self._runner.cleanup(),
                timeout=timeout,
            )
            logger.info("Albus server stopped cleanly")
        except TimeoutError:
            logger.warning(
                "Shutdown timed out after %ds — forcing close", timeout
            )
            # aiohttp's cleanup already does its best; nothing more to force
        except Exception as e:
            logger.error("Error during shutdown: %s", e, exc_info=True)

        self._runner = None


# =============================================================================
# BOOTSTRAP
# =============================================================================


async def _async_main(host: str, port: int, debug: bool) -> None:
    """Boot and run the server."""
    from stdlib.bootstrap import load_stdlib

    # 1. Load tools
    load_stdlib()
    registry = get_tool_registry()

    # 2. Load config
    deploy_config = DeploymentConfig.load()
    errors = deploy_config.validate_config()
    if errors:
        raise RuntimeError(f"Invalid config ({deploy_config.source}): {errors}")

    # 3. Build context + VM
    ctx = Context(tools={td.name: td.handler for td in registry.list() if td.handler})
    vm = PathwayVM(ctx)

    # 4. Create server
    server = await AlbusHTTPServer.create(pathway_vm=vm, debug=debug)

    # 5. Apply model routing from config (must happen before agent registration)
    if deploy_config.models and deploy_config.models.routing:
        apply_model_config(
            deploy_config.models.default_profile,
            deploy_config.models.routing,
        )

    # 6. Register agents from config (inline definitions)
    if deploy_config.agent_specs:
        await server.runtime.register_agents(deploy_config.agent_specs)

    # 6b. Load agents from directories (agent.yaml packages)
    if deploy_config.agent_directories:
        try:
            loaded = await load_agents_from_directories(
                deploy_config.agent_directories,
                server.runtime.agent_repo,
            )
            if loaded:
                logger.info("Loaded %d agents from directories", loaded)
        except Exception:
            logger.exception("Failed to load agents from directories")

    # 7. Load skills from config directories
    if deploy_config.skills and deploy_config.skills.directories:
        try:
            loaded = await server.runtime.load_skills(
                deploy_config.skills.directories,
                auto_activate=deploy_config.skills.auto_activate,
            )
            if loaded:
                logger.info("Loaded %d skills from config directories", loaded)
        except Exception:
            logger.exception("Failed to load skills from config")

    # 8. Load pathways from config directories
    if deploy_config.pathways_config and deploy_config.pathways_config.directories:
        try:
            loaded = await server.runtime.load_pathways(
                deploy_config.pathways_config.directories,
                auto_deploy=deploy_config.pathways_config.auto_deploy,
            )
            if loaded:
                logger.info("Loaded %d pathways from config directories", loaded)
        except Exception:
            logger.exception("Failed to load pathways from config")

    # 9. Run
    try:
        await server.start(host=host, port=port)
    except OSError as e:
        if getattr(e, "errno", None) in (48, 98):
            print(f"\nPort {port} already in use. Try: --port {port + 1}\n")
            return
        raise
    finally:
        await server.stop()


def main() -> None:
    """CLI entry point."""
    # Add src to path
    src_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
    if src_path not in sys.path:
        sys.path.insert(0, src_path)

    # Load .env
    project_root = Path(__file__).parent.parent.parent.parent
    for env_file in [project_root / ".env", project_root / ".env.local"]:
        if env_file.exists():
            load_dotenv(env_file, override=True)

    parser = argparse.ArgumentParser(description="Run Albus server")
    parser.add_argument("--host", default=os.getenv("ALBUS_HOST", "127.0.0.1"))
    parser.add_argument("--port", type=int, default=int(os.getenv("ALBUS_PORT", "8080")))
    parser.add_argument("--debug", action="store_true")
    parser.add_argument("--log-level", default="")
    args = parser.parse_args()

    configure_logging(debug=args.debug, log_level=args.log_level)

    asyncio.run(_async_main(args.host, args.port, args.debug))


if __name__ == "__main__":
    main()


__all__ = ["AlbusHTTPServer"]
