#!/usr/bin/env python3
"""
Oracle DB MCP Server - Main Entry Point

This is the main entry point for the Oracle Database MCP server.
It initializes the server and starts listening for MCP connections.
Supports streamable-http transport.
"""

import sys
import os
import asyncio
import logging
import argparse
from typing import Optional

# Add the project root to the Python path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from util.log import setup_logging


def parse_arguments():
    """Parse command line arguments."""
    parser = argparse.ArgumentParser(description="Oracle DB MCP Server")
    parser.add_argument(
        "--host",
        default="127.0.0.1",
        help="Host to bind HTTP server to (default: 127.0.0.1)",
    )
    parser.add_argument(
        "--port",
        type=int,
        default=7087,
        help="Port to bind HTTP server to (default: 7087)",
    )
    parser.add_argument(
        "--log-level",
        choices=["DEBUG", "INFO", "WARNING", "ERROR"],
        default="INFO",
        help="Logging level (default: INFO)",
    )
    return parser.parse_args()


def run_streamable_http_server(host: str, port: int, log_level: str):
    """Run the server using streamable-http transport.

    NOTE: fastmcp.run() uses anyio.run() internally which will attempt to
    start a new event loop. Callers running inside an existing asyncio
    loop (e.g. asyncio.run(main())) will hit "Already running asyncio in
    this thread". To avoid nested event loops we run fastmcp.run() in a
    separate thread via asyncio.to_thread() from the caller.
    """
    logger = logging.getLogger(__name__)
    logger.info(
        f"Starting Oracle DB MCP Server with streamable-http transport on {host}:{port}..."
    )

    # Import FastMCP for streamable-http support
    try:
        from mcp.server.fastmcp import FastMCP
    except ImportError as e:
        logger.error(f"FastMCP not available: {e}")
        logger.error("Please install fastapi, uvicorn, and sse-starlette")
        sys.exit(1)

    # Create Oracle server components
    from server.oracle.pool import OraclePool
    from server.oracle.exec import OracleExecutor
    from server.oracle.meta import OracleMetadata
    from server.oracle.plsql import OraclePLSQL

    # Initialize Oracle components
    pool = OraclePool()
    executor = OracleExecutor(pool)
    metadata = OracleMetadata(pool)
    plsql = OraclePLSQL(pool)

    # Create FastMCP server
    fastmcp = FastMCP(
        name="oracle-db", host=host, port=port, log_level=log_level, debug=False
    )

    # Register only the curated public tools per the new scope:
    # Expose three tools: sql.select, sql.execute, db.ping
    @fastmcp.tool(name="db.ping")
    async def db_ping():
        """Diagnostics: ping the database and return structured payload"""
        return pool.ping()
 
    @fastmcp.tool(name="sql.select")
    async def sql_select(sql: str, params: dict = None, max_rows: int = None, timeout_s: int = None):
        """Execute a SELECT statement and return columns + rows.
        Enforces SELECT-only semantics at the executor layer."""
        return await executor.sql_select(sql, params=params, timeout_s=timeout_s, max_rows=max_rows)
 
    @fastmcp.tool(name="sql.execute")
    async def sql_execute(sql: str, params: dict = None, timeout_s: int = None):
        """Execute DML/DDL or PL/SQL. Supports OUT bind auto-detection for PL/SQL."""
        return await executor.sql_execute(sql, params=params, timeout_s=timeout_s)
 
    # Internal helper tools (list_objects, describe_object, etc.) remain implemented
    # in server/oracle/* but are intentionally not exposed as public MCP tools.

    # When called from an existing asyncio loop, run the blocking fastmcp.run
    # inside a thread to avoid nested event loop errors. Callers that are not
    # running an event loop can call asyncio.run(run_streamable_http_server(...))
    # and the to_thread will still work.
    import functools

    def _run_fastmcp():
        fastmcp.run(transport="streamable-http")

    # Use asyncio.to_thread when called from async context to move blocking call
    # to a worker thread. Caller (main) currently awaits this function.
    return asyncio.to_thread(_run_fastmcp)


async def main():
    """Main entry point for the MCP server."""
    args = parse_arguments()

    # Setup logging with specified level
    setup_logging(args.log_level)
    logger = logging.getLogger(__name__)

    try:
        await run_streamable_http_server(args.host, args.port, args.log_level)
    except KeyboardInterrupt:
        logger.info("Server stopped by user")
    except Exception as e:
        # Log full traceback to help diagnose TaskGroup/unhandled exceptions
        logger.exception(f"Server error: {e}")
        # Exit with non-zero code so process supervisor / Roo sees failure
        sys.exit(1)


if __name__ == "__main__":
    asyncio.run(main())
