"""NotebookLM MCP Server - Modular Architecture.

This is the main server facade that initializes FastMCP and registers all tools
from the modular tools package. Tools are organized into domain-specific modules
under the `tools/` directory.

Tool Modules:
- auth.py: Authentication management (refresh_auth, save_auth_tokens)
- notebooks.py: Notebook CRUD operations
- sources.py: Source management with consolidated source_add
- sharing.py: Sharing and collaboration
- research.py: Deep research and source discovery
- studio.py: Artifact creation with consolidated studio_create
- downloads.py: Artifact downloads with consolidated download_artifact
- chat.py: Query and conversation management
- exports.py: Export artifacts to Google Docs/Sheets
- notes.py: Note management (create, list, update, delete)
"""

import argparse
import logging
import os

from fastmcp import FastMCP
from starlette.requests import Request
from starlette.responses import JSONResponse

from notebooklm_tools import __version__

_FALSY = frozenset({"false", "0", "no", "off"})


def _env_bool(name: str, default: bool = False) -> bool:
    """Read a boolean from an environment variable. Unset/empty → *default*; otherwise ``false|0|no|off`` (case-insensitive) → False, anything else → True."""
    raw = os.environ.get(name, "")
    if not raw:
        return default
    return raw.lower() not in _FALSY


# Initialize MCP server
mcp = FastMCP(
    name="notebooklm",
    instructions="""NotebookLM MCP - Access NotebookLM (notebooklm.google.com).

**Auth:** If you get authentication errors, run `nlm login` via your Bash/terminal tool. This is the automated authentication method that handles everything. Only use save_auth_tokens as a fallback if the CLI fails.
**Account Switching:** To switch Google Accounts for the MCP server, run `nlm login switch <profile>` in Bash. The MCP server instantly uses the active default profile.
**Confirmation:** Tools with confirm param require user approval before setting confirm=True.
**Studio:** After creating audio/video/infographic/slides, poll studio_status for completion.

Consolidated tools:
- source_add(source_type=url|text|drive|file, url=..., document_id=..., text=..., file_path=...): Add any source type
- studio_create(artifact_type=audio|video|...): Create any artifact type
- studio_revise: Revise individual slides in an existing slide deck
- download_artifact(artifact_type=audio|video|...): Download any artifact type
- note_create/note_list/note_update/note_delete: Manage notes in notebooks""",
)

# MCP request/response logger
mcp_logger = logging.getLogger("notebooklm_tools.mcp")


# Health check endpoint
@mcp.custom_route("/health", methods=["GET"])
async def health_check(request: Request) -> JSONResponse:
    """Health check endpoint for load balancers and monitoring."""
    return JSONResponse(
        {
            "status": "healthy",
            "service": "notebooklm-mcp",
            "version": __version__,
        }
    )


def _register_tools() -> None:
    """Import and register all tools from the modular tools package."""
    # Import all tool modules to populate the registry
    from .tools import (  # noqa: F401
        auth,
        batch,
        chat,
        cross_notebook,
        downloads,
        exports,
        labels,
        notebooks,
        notes,
        pipeline,
        research,
        sharing,
        smart_select,
        sources,
        studio,
        studio_advanced,
    )
    from .tools._utils import register_all_tools

    # Register collected tools with mcp
    register_all_tools(mcp)


# Register tools on import
_register_tools()


def main() -> None:
    """Run the MCP server.

    Supports multiple transports:
    - stdio (default): For desktop apps like Claude Desktop
    - http: Streamable HTTP for network access
    - sse: Legacy SSE transport (backwards compatibility)
    """
    from notebooklm_tools.utils.io_encoding import configure_stdio_utf8_on_windows

    configure_stdio_utf8_on_windows()

    parser = argparse.ArgumentParser(
        description="NotebookLM MCP Server",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Environment Variables:
  NOTEBOOKLM_MCP_TRANSPORT     Transport type (stdio, http, sse)
  NOTEBOOKLM_MCP_HOST          Host to bind (default: 127.0.0.1)
  NOTEBOOKLM_MCP_PORT          Port to listen on (default: 8000)
  NOTEBOOKLM_MCP_PATH          MCP endpoint path (default: /mcp)
  NOTEBOOKLM_MCP_STATELESS     Stateless HTTP sessions (default: true, set false to disable)
  NOTEBOOKLM_MCP_DEBUG         Debug logging (default: false)
  NOTEBOOKLM_HL                Interface language and default artifact language (default: en)
  NOTEBOOKLM_QUERY_TIMEOUT     Query timeout in seconds (default: 120.0)

Examples:
  notebooklm-mcp                              # Default stdio transport
  notebooklm-mcp --transport http             # HTTP on localhost:8000
  notebooklm-mcp --transport http --port 3000 # HTTP on custom port
  notebooklm-mcp --debug                      # Enable debug logging
        """,
    )

    parser.add_argument(
        "--transport",
        "-t",
        choices=["stdio", "http", "sse"],
        default=os.environ.get("NOTEBOOKLM_MCP_TRANSPORT", "stdio"),
        help="Transport protocol (default: stdio)",
    )
    parser.add_argument(
        "--host",
        "-H",
        default=os.environ.get("NOTEBOOKLM_MCP_HOST", "127.0.0.1"),
        help="Host to bind for HTTP/SSE (default: 127.0.0.1)",
    )
    parser.add_argument(
        "--port",
        "-p",
        type=int,
        default=int(os.environ.get("NOTEBOOKLM_MCP_PORT", "8000")),
        help="Port for HTTP/SSE transport (default: 8000)",
    )
    parser.add_argument(
        "--path",
        default=os.environ.get("NOTEBOOKLM_MCP_PATH", "/mcp"),
        help="MCP endpoint path for HTTP (default: /mcp)",
    )
    parser.add_argument(
        "--stateless",
        action=argparse.BooleanOptionalAction,
        default=_env_bool("NOTEBOOKLM_MCP_STATELESS", default=True),
        help="Stateless HTTP sessions (default: true). Avoids MCP SDK double-response crash (python-sdk#2416)",
    )
    parser.add_argument(
        "--debug",
        action=argparse.BooleanOptionalAction,
        default=_env_bool("NOTEBOOKLM_MCP_DEBUG"),
        help="Enable debug logging",
    )
    parser.add_argument(
        "--query-timeout",
        type=float,
        default=float(os.environ.get("NOTEBOOKLM_QUERY_TIMEOUT", "120.0")),
        help="Query timeout in seconds (default: 120.0)",
    )

    args = parser.parse_args()

    # Configure debug logging
    if args.debug:
        logging.basicConfig(
            level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
        )
        mcp_logger.setLevel(logging.DEBUG)
        # Also enable core client logging
        logging.getLogger("notebooklm_tools.core").setLevel(logging.DEBUG)

    # Set query timeout
    from .tools._utils import set_query_timeout

    set_query_timeout(args.query_timeout)

    # Run server with appropriate transport
    # show_banner=False prevents Rich box-drawing output that can corrupt
    # the JSON-RPC protocol on Windows (especially with non-English locales)
    import sys

    if args.transport == "stdio":

        class _StdoutToStderrWrapper:
            """Redirects sys.stdout.write to sys.stderr, but preserves original buffer.

            This ensures that stray print() statements and logs go to stderr and do not
            corrupt the MCP JSON-RPC protocol on stdout, while allowing the MCP SDK to
            write the JSON-RPC messages to the original stdout buffer.
            """

            def __init__(self, original_stdout):
                self._original_stdout = original_stdout
                self.buffer = getattr(original_stdout, "buffer", original_stdout)

            def write(self, s):
                return sys.stderr.write(s)

            def flush(self):
                sys.stderr.flush()

            def __getattr__(self, name):
                return getattr(self._original_stdout, name)

        sys.stdout = _StdoutToStderrWrapper(sys.stdout)

        mcp.run(show_banner=False)
    elif args.transport in ("http", "sse"):
        _LOOPBACK_HOSTS = {"127.0.0.1", "localhost", "::1"}
        if args.host not in _LOOPBACK_HOSTS:
            if not _env_bool("NOTEBOOKLM_ALLOW_EXTERNAL_BIND"):
                print(
                    f"SECURITY ERROR: Refusing to bind to non-loopback address '{args.host}'.\n"
                    "There is no built-in authentication — binding externally exposes\n"
                    "your Google cookies to anyone on the network.\n\n"
                    "To override, set: NOTEBOOKLM_ALLOW_EXTERNAL_BIND=1",
                    file=sys.stderr,
                )
                sys.exit(1)
            import warnings

            warnings.warn(
                f"SECURITY WARNING: {args.transport.upper()} transport is bound to a "
                f"non-loopback address ('{args.host}'). There is no built-in "
                "authentication. Do not expose this port to untrusted networks.",
                stacklevel=2,
            )
        if args.transport == "http":
            mcp.run(
                transport="streamable-http",
                host=args.host,
                port=args.port,
                path=args.path,
                stateless_http=args.stateless,
                show_banner=False,
            )
        else:
            mcp.run(
                transport="sse",
                host=args.host,
                port=args.port,
                show_banner=False,
            )


if __name__ == "__main__":
    main()
