"""
Server module for running Hindsight in a background thread.

Provides a simple way to start and stop the Hindsight HTTP API server
without blocking the main thread.
"""
import asyncio
import logging
import socket
import threading
import time
from typing import Optional

import uvicorn
from uvicorn import Config

from hindsight_api import MemoryEngine
from hindsight_api.api import create_app

logger = logging.getLogger(__name__)


def _find_free_port() -> int:
    """Find a free port on localhost."""
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind(("", 0))
        s.listen(1)
        port = s.getsockname()[1]
    return port


class Server:
    """
    Hindsight server that runs in a background thread.

    Example:
        ```python
        from hindsight import Server

        server = Server(
            db_url="pg0",
            llm_provider="groq",
            llm_api_key="your-api-key",
            llm_model="openai/gpt-oss-120b"
        )
        server.start()

        print(f"Server running at {server.url}")

        # Use the server...

        server.stop()
        ```
    """

    def __init__(
        self,
        db_url: str = "pg0",
        llm_provider: str = "groq",
        llm_api_key: str = "",
        llm_model: str = "openai/gpt-oss-120b",
        llm_base_url: Optional[str] = None,
        host: str = "127.0.0.1",
        port: Optional[int] = None,
        mcp_enabled: bool = False,
        log_level: str = "info",
    ):
        """
        Initialize the Hindsight server.

        Args:
            db_url: Database URL. Use "pg0" for embedded PostgreSQL.
            llm_provider: LLM provider ("groq", "openai", "ollama", "gemini", "anthropic", "lmstudio")
            llm_api_key: API key for the LLM provider
            llm_model: Model name to use
            llm_base_url: Optional custom base URL for LLM API
            host: Host to bind to (default: 127.0.0.1)
            port: Port to bind to (default: auto-select free port)
            mcp_enabled: Whether to enable MCP server
            log_level: Uvicorn log level (default: warning)
        """
        self.db_url = db_url
        self.llm_provider = llm_provider
        self.llm_api_key = llm_api_key
        self.llm_model = llm_model
        self.llm_base_url = llm_base_url
        self.host = host
        self.port = port or _find_free_port()
        self.mcp_enabled = mcp_enabled
        self.log_level = log_level

        self._memory: Optional[MemoryEngine] = None
        self._server: Optional[uvicorn.Server] = None
        self._thread: Optional[threading.Thread] = None
        self._started = threading.Event()
        self._stopped = threading.Event()

    @property
    def url(self) -> str:
        """Get the server URL."""
        return f"http://{self.host}:{self.port}"

    def _run_server(self):
        """Run the server in a background thread."""
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        try:
            # Create MemoryEngine
            self._memory = MemoryEngine(
                db_url=self.db_url,
                memory_llm_provider=self.llm_provider,
                memory_llm_api_key=self.llm_api_key,
                memory_llm_model=self.llm_model,
                memory_llm_base_url=self.llm_base_url,
            )

            # Create FastAPI app
            app = create_app(
                memory=self._memory,
                mcp_api_enabled=self.mcp_enabled,
                initialize_memory=True,
            )

            # Create uvicorn config and server
            config = Config(
                app=app,
                host=self.host,
                port=self.port,
                log_level=self.log_level,
                loop="asyncio",
            )
            self._server = uvicorn.Server(config)

            # Signal that we're starting
            self._started.set()

            # Run the server
            loop.run_until_complete(self._server.serve())

        except Exception as e:
            logger.error(f"Server error: {e}")
            raise
        finally:
            # Cleanup
            if self._memory:
                loop.run_until_complete(self._memory.close())
            loop.close()
            self._stopped.set()

    def start(self, timeout: float = 30.0) -> "Server":
        """
        Start the server in a background thread.

        Args:
            timeout: Maximum time to wait for server to start (seconds)

        Returns:
            self (for chaining)

        Raises:
            RuntimeError: If server fails to start within timeout
        """
        if self._thread is not None and self._thread.is_alive():
            raise RuntimeError("Server is already running")

        self._started.clear()
        self._stopped.clear()

        self._thread = threading.Thread(target=self._run_server, daemon=True)
        self._thread.start()

        # Wait for server to start
        self._started.wait(timeout=timeout)

        # Give uvicorn a moment to actually bind to the port
        start_time = time.time()
        while time.time() - start_time < timeout:
            try:
                with socket.create_connection((self.host, self.port), timeout=1):
                    logger.info(f"Hindsight server started at {self.url}")
                    return self
            except (ConnectionRefusedError, socket.timeout, OSError):
                time.sleep(0.1)

        raise RuntimeError(f"Server failed to start within {timeout} seconds")

    def stop(self, timeout: float = 10.0) -> None:
        """
        Stop the server.

        Args:
            timeout: Maximum time to wait for server to stop (seconds)
        """
        if self._server is None:
            return

        # Signal uvicorn to shutdown
        self._server.should_exit = True

        # Wait for thread to finish
        if self._thread is not None:
            self._thread.join(timeout=timeout)
            if self._thread.is_alive():
                logger.warning("Server thread did not stop cleanly")

        self._server = None
        self._thread = None
        logger.info("Hindsight server stopped")

    def __enter__(self) -> "Server":
        """Context manager entry."""
        return self.start()

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        """Context manager exit."""
        self.stop()


def start_server(
    db_url: str = "pg0",
    llm_provider: str = "groq",
    llm_api_key: str = "",
    llm_model: str = "openai/gpt-oss-120b",
    llm_base_url: Optional[str] = None,
    host: str = "127.0.0.1",
    port: Optional[int] = None,
    mcp_enabled: bool = False,
    log_level: str = "warning",
    timeout: float = 30.0,
) -> Server:
    """
    Start a Hindsight server in a background thread.

    This is a convenience function that creates and starts a Server instance.

    Args:
        db_url: Database URL. Use "pg0" for embedded PostgreSQL.
        llm_provider: LLM provider ("groq", "openai", "ollama", "gemini", "anthropic", "lmstudio")
        llm_api_key: API key for the LLM provider
        llm_model: Model name to use
        llm_base_url: Optional custom base URL for LLM API
        host: Host to bind to (default: 127.0.0.1)
        port: Port to bind to (default: auto-select free port)
        mcp_enabled: Whether to enable MCP server
        log_level: Uvicorn log level (default: warning)
        timeout: Maximum time to wait for server to start (seconds)

    Returns:
        Running Server instance

    Example:
        ```python
        from hindsight import start_server, Client

        server = start_server(
            db_url="pg0",
            llm_provider="groq",
            llm_api_key="your-api-key",
            llm_model="openai/gpt-oss-120b"
        )

        client = Client(base_url=server.url)
        client.put(agent_id="assistant", content="User likes Python")

        server.stop()
        ```
    """
    server = Server(
        db_url=db_url,
        llm_provider=llm_provider,
        llm_api_key=llm_api_key,
        llm_model=llm_model,
        llm_base_url=llm_base_url,
        host=host,
        port=port,
        mcp_enabled=mcp_enabled,
        log_level=log_level,
    )
    return server.start(timeout=timeout)
