# -*- coding: utf-8 -*-
"""Location: ./mcpgateway/cli.py
Copyright 2026
SPDX-License-Identifier: Apache-2.0
Authors: Mihai Criveti

mcpgateway CLI ─ a thin wrapper around Uvicorn
This module is exposed as a **console-script** via:

    [project.scripts]
    mcpgateway = "mcpgateway.cli:main"

so that a user can simply type `mcpgateway ...` instead of the longer
`uvicorn mcpgateway.main:app ...`.

Features
─────────
* Injects the default FastAPI application path (``mcpgateway.main:app``)
  when the user doesn't supply one explicitly.
* Adds sensible default host/port (127.0.0.1:4444) unless the user passes
  ``--host``/``--port`` or overrides them via the environment variables
  ``MCG_HOST`` and ``MCG_PORT``.
* Forwards *all* remaining arguments verbatim to Uvicorn's own CLI, so
  `--reload`, `--workers`, etc. work exactly the same.

Typical usage
─────────────
```console
$ mcpgateway --reload                 # dev server on 127.0.0.1:4444
$ mcpgateway --workers 4              # production-style multiprocess
$ mcpgateway 127.0.0.1:8000 --reload  # explicit host/port keeps defaults out
$ mcpgateway mypkg.other:app          # run a different ASGI callable
```
"""

# Future
from __future__ import annotations

# Standard
import os
from pathlib import Path
import sys
from typing import List, Optional

# Third-Party
import orjson
from pydantic import ValidationError
import uvicorn

# First-Party
from mcpgateway import __version__
from mcpgateway.config import Settings

# ---------------------------------------------------------------------------
# Configuration defaults (overridable via environment variables)
# ---------------------------------------------------------------------------
DEFAULT_APP = "mcpgateway.main:app"  # dotted path to FastAPI instance
DEFAULT_HOST = os.getenv("MCG_HOST", "127.0.0.1")
DEFAULT_PORT = int(os.getenv("MCG_PORT", "4444"))

# ---------------------------------------------------------------------------
# Helper utilities
# ---------------------------------------------------------------------------


def _needs_app(arg_list: List[str]) -> bool:
    """Return *True* when the CLI invocation has *no* positional APP path.

    According to Uvicorn's argument grammar, the **first** non-flag token
    is taken as the application path. We therefore look at the first
    element of *arg_list* (if any) - if it *starts* with a dash it must be
    an option, hence the app path is missing and we should inject ours.

    Args:
        arg_list (List[str]): List of arguments

    Returns:
        bool: Returns *True* when the CLI invocation has *no* positional APP path

    Examples:
        >>> _needs_app([])
        True
        >>> _needs_app(["--reload"])
        True
        >>> _needs_app(["myapp.main:app"])
        False
    """

    return len(arg_list) == 0 or arg_list[0].startswith("-")


def _insert_defaults(raw_args: List[str]) -> List[str]:
    """Return a *new* argv with defaults sprinkled in where needed.

    Args:
        raw_args (List[str]): List of input arguments to cli

    Returns:
        List[str]: List of arguments

    Examples:
        >>> result = _insert_defaults([])
        >>> result[0]
        'mcpgateway.main:app'
        >>> result = _insert_defaults(["myapp.main:app", "--reload"])
        >>> result[0]
        'myapp.main:app'
    """

    args = list(raw_args)  # shallow copy - we'll mutate this

    # 1️⃣  Ensure an application path is present.
    if _needs_app(args):
        args.insert(0, DEFAULT_APP)

    # 2️⃣  Supply host/port if neither supplied nor UNIX domain socket.
    if "--uds" not in args:
        if "--host" not in args and "--http" not in args:
            args.extend(["--host", DEFAULT_HOST])
        if "--port" not in args:
            args.extend(["--port", str(DEFAULT_PORT)])

    return args


def _handle_validate_config(path: str = ".env") -> None:
    """
    Validate the application's environment configuration file.

    Attempts to load settings from the specified .env file using Pydantic.
    If validation fails, prints the errors and exits with code 1.
    On success, prints a confirmation message.

    Args:
        path (str): Path to the .env file to validate. Defaults to ".env".

    Raises:
        SystemExit: Exits with code 1 if the configuration is invalid.

    Examples:
        >>> _handle_validate_config(".env.example")
        ✅ Configuration in .env.example is valid
    """

    try:
        Settings(_env_file=path)
    except ValidationError as exc:
        print(f"❌ Invalid configuration in {path}", file=sys.stderr)
        print(exc.json(indent=2), file=sys.stderr)
        raise SystemExit(1)

    print(f"✅ Configuration in {path} is valid")


def _handle_config_schema(output: Optional[str] = None) -> None:
    """
    Export the JSON schema for ContextForge Settings.

    This function serializes the Pydantic Settings model into a JSON Schema
    suitable for validation or documentation purposes.

    Args:
        output (Optional[str]): Optional file path to write the schema.
            If None, prints to stdout.

    Examples:
        >>> # Print schema to stdout (output truncated for doctest)
        >>> _handle_config_schema()  # doctest: +ELLIPSIS
        {...

        >>> # Write schema to a file (creates 'schema.json'), skip doctest
        >>> _handle_config_schema("schema.json")  # doctest: +SKIP
        ✅ Schema written to schema.json
    """
    schema = Settings.model_json_schema(mode="validation")
    data = orjson.dumps(schema, option=orjson.OPT_INDENT_2 | orjson.OPT_SORT_KEYS).decode()

    if output:
        path = Path(output)
        path.write_text(data, encoding="utf-8")
        print(f"✅ Schema written to {path}")
    else:
        print(data)


def _handle_support_bundle(
    output_dir: Optional[str] = None,
    log_lines: int = 1000,
    include_logs: bool = True,
    include_env: bool = True,
    include_system: bool = True,
) -> None:
    """
    Generate a support bundle containing diagnostics and logs.

    Creates a ZIP file with version info, system diagnostics, configuration,
    and logs - all automatically sanitized to remove sensitive data like
    passwords, tokens, and API keys.

    Args:
        output_dir (Optional[str]): Directory for bundle output (default: /tmp)
        log_lines (int): Number of log lines to include (default: 1000, 0 = all)
        include_logs (bool): Include log files (default: True)
        include_env (bool): Include environment config (default: True)
        include_system (bool): Include system info (default: True)

    Raises:
        SystemExit: If bundle generation fails

    Examples:
        >>> # Generate bundle with default settings
        >>> _handle_support_bundle()  # doctest: +SKIP
        ✅ Support bundle created: /tmp/mcpgateway-support-2025-01-09-120000.zip

        >>> # Generate bundle with custom settings
        >>> _handle_support_bundle(output_dir="/tmp", log_lines=500)  # doctest: +SKIP
        ✅ Support bundle created: /tmp/mcpgateway-support-2025-01-09-120000.zip
    """
    # First-Party
    from mcpgateway.services.support_bundle_service import SupportBundleConfig, SupportBundleService  # pylint: disable=import-outside-toplevel

    try:
        config = SupportBundleConfig(
            include_logs=include_logs,
            include_env=include_env,
            include_system_info=include_system,
            log_tail_lines=log_lines,
            output_dir=Path(output_dir) if output_dir else None,
        )

        service = SupportBundleService()
        bundle_path = service.generate_bundle(config)

        print(f"✅ Support bundle created: {bundle_path}")
        print(f"📦 Bundle size: {bundle_path.stat().st_size / 1024:.2f} KB")
        print()
        print("⚠️  Security Notice:")
        print("   The bundle has been sanitized, but please review before sharing.")
        print("   Sensitive data (passwords, tokens, secrets) have been redacted.")
    except Exception as exc:
        print(f"❌ Failed to create support bundle: {exc}", file=sys.stderr)
        raise SystemExit(1)


# ---------------------------------------------------------------------------
# Public entry-point
# ---------------------------------------------------------------------------


def main() -> None:  # noqa: D401 - imperative mood is fine here
    """Entry point for the *mcpgateway* console script (delegates to Uvicorn).

    Processes command line arguments, handles version requests, and forwards
    all other arguments to Uvicorn with sensible defaults injected.

    Also supports export/import subcommands for configuration management.

    Environment Variables:
        MCG_HOST: Default host (default: "127.0.0.1")
        MCG_PORT: Default port (default: "4444")

    Usage:
        mcpgateway --reload
        mcpgateway --workers 4
        mcpgateway --validate-config [path]
        mcpgateway --config-schema [output]
        mcpgateway --support-bundle [options]

    Flags:
        --validate-config [path]           Validate .env file (default: .env)
        --config-schema [output]           Print or write JSON schema for Settings
        --support-bundle                   Generate support bundle for troubleshooting
            --output-dir [path]            Output directory (default: /tmp)
            --log-lines [n]                Number of log lines (default: 1000, 0 = all)
            --no-logs                      Exclude log files
            --no-env                       Exclude environment config
            --no-system                    Exclude system info
    """

    # Check for export/import commands first
    if len(sys.argv) > 1 and sys.argv[1] in ["export", "import"]:
        # Avoid cyclic import by importing only when needed
        # First-Party
        from mcpgateway.cli_export_import import main_with_subcommands  # pylint: disable=import-outside-toplevel,cyclic-import

        main_with_subcommands()
        return

    # Check for version flag
    if "--version" in sys.argv or "-V" in sys.argv:
        print(f"mcpgateway {__version__}")
        return

    # Handle config-related flags
    if len(sys.argv) > 1:
        cmd = sys.argv[1]

        if cmd == "--validate-config":
            env_path = sys.argv[2] if len(sys.argv) > 2 else ".env"
            _handle_validate_config(env_path)
            return

        if cmd == "--config-schema":
            output = sys.argv[2] if len(sys.argv) > 2 else None
            _handle_config_schema(output)
            return

        if cmd == "--support-bundle":
            # Parse support bundle options
            output_dir = None
            log_lines = 1000
            include_logs = True
            include_env = True
            include_system = True

            i = 2
            while i < len(sys.argv):
                arg = sys.argv[i]
                if arg == "--output-dir" and i + 1 < len(sys.argv):
                    output_dir = sys.argv[i + 1]
                    i += 2
                elif arg == "--log-lines" and i + 1 < len(sys.argv):
                    log_lines = int(sys.argv[i + 1])
                    i += 2
                elif arg == "--no-logs":
                    include_logs = False
                    i += 1
                elif arg == "--no-env":
                    include_env = False
                    i += 1
                elif arg == "--no-system":
                    include_system = False
                    i += 1
                else:
                    i += 1

            _handle_support_bundle(
                output_dir=output_dir,
                log_lines=log_lines,
                include_logs=include_logs,
                include_env=include_env,
                include_system=include_system,
            )
            return

    # Discard the program name and inspect the rest.
    user_args = sys.argv[1:]
    uvicorn_argv = _insert_defaults(user_args)

    # Uvicorn's `main()` uses sys.argv - patch it in and run.
    sys.argv = ["mcpgateway", *uvicorn_argv]
    uvicorn.main()  # pylint: disable=no-value-for-parameter


if __name__ == "__main__":  # pragma: no cover - executed only when run directly
    main()
