"""Test server for SSE-based message streaming to Yera UI.

This is a standalone test server for UI development and testing.
The streaming functionality will eventually be integrated into the main ApiServer.
"""

import logging
import mimetypes
import os
from pathlib import Path

from fastapi import Body, FastAPI, HTTPException
from fastapi import Path as FastAPIPath
from fastapi.responses import FileResponse, StreamingResponse
from fastapi.staticfiles import StaticFiles

from infra_mvp.base_server import BaseServer
from infra_mvp.monitoring import ServiceMetrics, get_metrics_endpoint
from infra_mvp.stream.schemas import (
    AddInteractionResponse,
    AgentMetadata,
    CreateSessionResponse,
    ListAgentsResponse,
    SessionCreateRequest,
    UserInteraction,
)
from infra_mvp.stream.services import AgentService, SessionService


class StreamServer(BaseServer):
    """Server that streams predefined conversation events via Server-Sent Events (SSE).

    This server is intended for UI testing and development.  The streaming functionality
    will be integrated into the main ApiServer in the future.
    """

    def __init__(
        self,
        agent_service: AgentService,
        session_service: SessionService,
        host: str | None = "0.0.0.0",
        port: int | None = 8991,
        dev_ui: bool = False,
    ):
        """Initialise the stream server.

        Args:
            agent_service: Agent management service.
            session_service: Session management service.
            host: Server host address.  Defaults to "0.0.0.0".
            port: Server port.  Defaults to 8991 to avoid conflicts with other servers.

        """
        # Store services and dependencies
        self.agent_service = agent_service
        self.session_service = session_service
        self.dev_ui = dev_ui

        # Initialize metrics
        self.metrics = ServiceMetrics("stream")

        super().__init__(host, port)

    def stop(self):
        """Stop the server and clean up all sessions."""
        # Clean up all sessions (terminates agent processes)
        self.session_service.cleanup_all()
        super().stop()

    def build_api(self) -> FastAPI:
        """Build the FastAPI application with SSE streaming endpoints.

        Returns:
            Configured FastAPI application

        """
        app = FastAPI(
            title="Yera Stream Server",
            description="SSE-based message streaming server for Yera UI development",
            version="0.1.0",
        )

        @app.post("/api/session")
        async def create_session(
            request: SessionCreateRequest = Body(...),
        ) -> CreateSessionResponse:
            """Create a new session for streaming an agent.

            Args:
                request: Session creation request with agent_id

            Returns:
                Response with session_id

            Raises:
                HTTPException: 404 if agent not found

            """
            try:
                agent = self.agent_service.get_agent(request.agent_id)
            except ValueError as e:
                raise HTTPException(status_code=404, detail=str(e)) from e

            session_id = self.session_service.create_session(agent)
            return CreateSessionResponse(session_id=session_id)

        @app.get("/api/session/{session_id}/stream")
        async def stream_session(
            session_id: str = FastAPIPath(
                description="Session identifier for streaming"
            ),
        ) -> StreamingResponse:
            """Start streaming conversation events for a session via Server-Sent Events (SSE).

            Args:
                session_id: Session identifier from the URL path.

            Returns:
                StreamingResponse with text/event-stream content type

            Raises:
                HTTPException: 404 if session not found

            """
            try:
                self.session_service.get_session(session_id)
            except KeyError:
                raise HTTPException(
                    status_code=404, detail="Session not found"
                ) from None

            return StreamingResponse(
                self.session_service.generate_events(session_id),
                media_type="text/event-stream",
                headers={
                    "Cache-Control": "no-cache",
                    "Connection": "keep-alive",
                    "X-Accel-Buffering": "no",  # Disable buffering in nginx
                },
            )

        @app.post("/api/session/{session_id}/interaction")
        async def add_interaction(
            session_id: str = FastAPIPath(description="Session identifier"),
            interaction: UserInteraction = Body(description="User interaction data"),
        ) -> AddInteractionResponse:
            """Add a user interaction to a session.

            Args:
                session_id: Session identifier from the URL path
                interaction: User interaction data with blockId, value, and blockType

            Returns:
                Response indicating interaction was received

            Raises:
                HTTPException: 404 if session not found

            """
            logging.info(
                "Received user input: session_id=%s blockId=%s blockType=%s value=%s",
                session_id,
                interaction.blockId,
                interaction.blockType,
                interaction.value,
            )
            try:
                self.session_service.add_interaction(
                    session_id, interaction.model_dump()
                )
            except KeyError as e:
                raise HTTPException(
                    status_code=404, detail=f"Session not found: {session_id}"
                ) from e
            return AddInteractionResponse(status="received", session_id=session_id)

        @app.get("/api/agents", response_model=ListAgentsResponse)
        async def list_agents() -> ListAgentsResponse:
            """List all available agents with minimal metadata.

            The React UI uses this endpoint to:
            - Populate the home page with available agents.
            - Resolve `agent_id` strings used in `/agents/{id}` routes.

            There is no baked-in list of agents in the static HTML; the
            set of agents is discovered at runtime (see
            `yera.agents.discovery.discover_agents`) based on the directory
            in which `yera dev` is run.

            Returns:
                List of agents with minimal metadata for listing/navigation.

            """
            return ListAgentsResponse(
                agents=[
                    AgentMetadata(
                        id=agent.metadata.identifier,
                        name=agent.metadata.name,
                        description=agent.metadata.description,
                        path=[],
                    )
                    for agent in self.agent_service.list_agents()
                ]
            )

        @app.get("/health")
        async def health_check() -> dict[str, str]:
            """Health check endpoint.

            Returns:
                Dictionary with health status

            """
            self.metrics.track_request("/health", "GET", 200)
            return {"status": "healthy", "service": "yera-stream"}

        # Add metrics endpoint
        app.get("/metrics")(get_metrics_endpoint(self.metrics))

        # Optionally configure static UI serving (must be after API routes)
        self._configure_ui_routes(app)

        return app

    def _configure_ui_routes(self, app: FastAPI) -> None:
        """Configure static UI and SPA routing if enabled."""
        if self.dev_ui:
            return

        ui_path = _get_ui_static_path()
        if not ui_path:
            logging.error(
                "Static UI requested (dev_ui=False) but no UI dist directory was found. "
                "Ensure the yera UI bundle is installed or run in dev mode with the "
                "Next.js dev server."
            )
            # Fail fast so callers know UI is unavailable in this mode
            raise RuntimeError("Static UI not available")
        logging.info("Serving static UI from %s", ui_path)

        index_path = ui_path / "index.html"
        if not index_path.is_file():
            logging.error(
                "Static UI requested (dev_ui=False) but index.html was not found at %s",
                index_path,
            )
            raise RuntimeError("Static UI index.html not found")

        # Ensure the browser revalidates the SPA entry so users see the latest UI
        # after rebuilds or Yera upgrades (avoids stale cached index.html).
        html_headers = {"Cache-Control": "no-cache"}

        static_next = ui_path / "_next"
        if static_next.is_dir():
            app.mount(
                "/_next",
                StaticFiles(directory=str(static_next)),
                name="next-static",
            )

        favicon_path = ui_path / "favicon.ico"

        if favicon_path.is_file():

            @app.get("/favicon.ico")
            async def favicon() -> FileResponse:
                return FileResponse(favicon_path)

        @app.get("/")
        async def serve_root() -> FileResponse:
            """Serve the SPA entrypoint for the root path."""
            return FileResponse(index_path, headers=html_headers)

        @app.get("/{full_path:path}")
        async def serve_spa(full_path: str) -> FileResponse:
            """Serve static assets when they exist, otherwise serve the SPA entry.

            FastAPI registers more specific routes (API endpoints, /health, /metrics)
            before this catch-all handler, so those are matched first. This handler
            only receives routes that didn't match any other endpoint, and it acts
            as a smart fallback:

            - If the requested path corresponds to an actual file within the UI dist,
              that file is served directly.
            - Otherwise, index.html is served so the SPA router can handle the path
              client-side.

            The React UI is built as a single-page application (Next.js static export)
            that handles both the home view (\"/\") and agent views (\"/agents/{id}\").
            Agent metadata and availability are discovered dynamically in Python via
            `discover_agents` and exposed over `/api/agents` and `/api/session`;
            there are no per-agent HTML files, by design.
            """

            # Resolve path safely to avoid directory traversal outside ui_path
            try:
                candidate = (ui_path / full_path).resolve()
                # Ensure candidate is within ui_path
                if not candidate.is_relative_to(ui_path):
                    return FileResponse(index_path, headers=html_headers)
            except (OSError, RuntimeError, ValueError):
                # Any issues resolving the path - fall back to SPA entry
                return FileResponse(index_path, headers=html_headers)

            # If it's an actual file in the UI bundle, serve it with a sensible content type
            if candidate.is_file():
                media_type, _ = mimetypes.guess_type(str(candidate))
                return FileResponse(candidate, media_type=media_type)

            # Otherwise, let the SPA handle it
            return FileResponse(index_path, headers=html_headers)


def _get_ui_static_path() -> Path | None:
    """Locate the bundled UI static files, if available.

    Order of precedence (explicit over implicit):
    1. Environment variable YERA_UI_DIST
    2. Packaged path relative to installed yera package: yera/ui/dist
    """
    # First: explicit user configuration
    env_path = os.getenv("YERA_UI_DIST")
    if env_path:
        candidate = Path(env_path).expanduser()
        if candidate.is_dir() and (candidate / "index.html").is_file():
            return candidate

    # Second: packaged path (installed yera)
    try:
        import yera

        package_root = Path(yera.__file__).parent
        package_ui = package_root / "ui" / "dist"
        if package_ui.is_dir() and (package_ui / "index.html").is_file():
            return package_ui
    except (ImportError, AttributeError, TypeError):
        # yera not installed or __file__ not available (unlikely but safe)
        pass

    return None
